From 165ffc6e9681ef276a31799f9f6728782364e5d2 Mon Sep 17 00:00:00 2001 From: Uriel Acioli Date: Thu, 12 Sep 2024 05:11:38 -0300 Subject: [PATCH 1/9] feat(pyproject.toml): add pyproject.toml to project --- pyproject.toml | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..6ad7fb9c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,51 @@ +[project] +name = "cortix" +version = "1.1.43" +description = "Cortix is a Python library for network dynamics modeling and HPC simulation." +authors = [ + { name = "Valmor F. de Almeida", email = "valmor_dealmeida@uml.edu" }, +] +maintainers = [{ name = "Uriel Acioli", email = "uriel.acioli@gmail.com" }] +keywords = ['simulation', 'math'] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + 'Topic :: Scientific/Engineering :: Mathematics', + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'Intended Audience :: End Users/Desktop', + 'Intended Audience :: Science/Research', + 'Operating System :: MacOS', + 'Operating System :: Unix', + 'Topic :: Education', + 'Topic :: Utilities', +] +dependencies = [ + "pandas", + "matplotlib", + "numpy", + "scipy", + "pytest", + "graphviz", + "networkx>=3.3", +] +requires-python = "==3.11.*" +readme = "README.md" +license = { file = "LICENSE.txt" } + + +[tool.pdm] +distribution = false +[tool.pdm.scripts] +lint = { cmd = [ + 'ruff', + 'check', + '--output-format=concise', + './tests', + './cortix', + './examples', +] } +format = { cmd = ['ruff', 'format', './tests', './cortix', './examples'] } + +[tool.pdm.dev-dependencies] +dev = ["ruff>=0.6.4"] From 918a7f69ffe7b0d76da23b34d6cffcc2bc76e647 Mon Sep 17 00:00:00 2001 From: Uriel Acioli Date: Thu, 12 Sep 2024 05:13:49 -0300 Subject: [PATCH 2/9] refactor(cortix/docs): move cortix's documentation to folder inside project root --- cortix/docs/_templates/globaltoc.html | 46 ------------------ cortix/docs/cortix-cover.png | Bin 276517 -> 0 bytes {cortix/docs => docs}/Makefile | 0 {cortix/docs => docs}/build_docs.sh | 0 {cortix/docs => docs}/conf.py | 0 {cortix/docs => docs}/contents.rst | 0 .../city_justice_rst/adjudication.rst | 0 .../city_justice_rst/arrested.rst | 0 .../city_justice_rst/community.rst | 0 .../examples_rst/city_justice_rst/jail.rst | 0 .../examples_rst/city_justice_rst/modules.rst | 0 .../examples_rst/city_justice_rst/parole.rst | 0 .../examples_rst/city_justice_rst/prison.rst | 0 .../city_justice_rst/probation.rst | 0 .../city_justice_rst/run_city_justice.rst | 0 .../droplet_swirl_rst/droplet.rst | 0 .../droplet_swirl_rst/modules.rst | 0 .../droplet_swirl_rst/run_droplet_swirl.rst | 0 .../examples_rst/droplet_swirl_rst/vortex.rst | 0 .../docs => docs}/examples_rst/examples.rst | 0 .../docs => docs}/examples_rst/modules.rst | 0 .../examples_rst/nbody_rst/body.rst | 0 .../examples_rst/nbody_rst/modules.rst | 0 .../examples_rst/nbody_rst/run_planets.rst | 0 {cortix/docs => docs}/index.rst | 0 {cortix/docs => docs}/install.rst | 0 {cortix/docs => docs}/license.rst | 0 {cortix/docs => docs}/logo-old.jpg | Bin {cortix/docs => docs}/logo.jpg | Bin {cortix/docs => docs}/src_rst/cortix_main.rst | 0 {cortix/docs => docs}/src_rst/module.rst | 0 {cortix/docs => docs}/src_rst/modules.rst | 0 {cortix/docs => docs}/src_rst/network.rst | 0 {cortix/docs => docs}/src_rst/node.rst | 0 {cortix/docs => docs}/src_rst/port.rst | 0 {cortix/docs => docs}/support_rst/modules.rst | 0 .../support_rst/nuclear_rst/actor.rst | 0 .../support_rst/nuclear_rst/fuel_bucket.rst | 0 .../support_rst/nuclear_rst/fuel_bundle.rst | 0 .../support_rst/nuclear_rst/fuel_segment.rst | 0 .../nuclear_rst/fuelsegmentsgroups.rst | 0 .../support_rst/nuclear_rst/fuelslug.rst | 0 .../support_rst/nuclear_rst/modules.rst | 0 .../support_rst/nuclear_rst/nuclides.rst | 0 .../support_rst/periodictable.rst | 0 {cortix/docs => docs}/support_rst/phase.rst | 0 .../docs => docs}/support_rst/phase_new.rst | 0 .../support_rst/phase_newest.rst | 0 .../docs => docs}/support_rst/quantity.rst | 0 {cortix/docs => docs}/support_rst/specie.rst | 0 {cortix/docs => docs}/support_rst/species.rst | 0 {cortix/docs => docs}/support_rst/stream.rst | 0 52 files changed, 46 deletions(-) delete mode 100644 cortix/docs/_templates/globaltoc.html delete mode 100644 cortix/docs/cortix-cover.png rename {cortix/docs => docs}/Makefile (100%) rename {cortix/docs => docs}/build_docs.sh (100%) rename {cortix/docs => docs}/conf.py (100%) rename {cortix/docs => docs}/contents.rst (100%) rename {cortix/docs => docs}/examples_rst/city_justice_rst/adjudication.rst (100%) rename {cortix/docs => docs}/examples_rst/city_justice_rst/arrested.rst (100%) rename {cortix/docs => docs}/examples_rst/city_justice_rst/community.rst (100%) rename {cortix/docs => docs}/examples_rst/city_justice_rst/jail.rst (100%) rename {cortix/docs => docs}/examples_rst/city_justice_rst/modules.rst (100%) rename {cortix/docs => docs}/examples_rst/city_justice_rst/parole.rst (100%) rename {cortix/docs => docs}/examples_rst/city_justice_rst/prison.rst (100%) rename {cortix/docs => docs}/examples_rst/city_justice_rst/probation.rst (100%) rename {cortix/docs => docs}/examples_rst/city_justice_rst/run_city_justice.rst (100%) rename {cortix/docs => docs}/examples_rst/droplet_swirl_rst/droplet.rst (100%) rename {cortix/docs => docs}/examples_rst/droplet_swirl_rst/modules.rst (100%) rename {cortix/docs => docs}/examples_rst/droplet_swirl_rst/run_droplet_swirl.rst (100%) rename {cortix/docs => docs}/examples_rst/droplet_swirl_rst/vortex.rst (100%) rename {cortix/docs => docs}/examples_rst/examples.rst (100%) rename {cortix/docs => docs}/examples_rst/modules.rst (100%) rename {cortix/docs => docs}/examples_rst/nbody_rst/body.rst (100%) rename {cortix/docs => docs}/examples_rst/nbody_rst/modules.rst (100%) rename {cortix/docs => docs}/examples_rst/nbody_rst/run_planets.rst (100%) rename {cortix/docs => docs}/index.rst (100%) rename {cortix/docs => docs}/install.rst (100%) rename {cortix/docs => docs}/license.rst (100%) rename {cortix/docs => docs}/logo-old.jpg (100%) rename {cortix/docs => docs}/logo.jpg (100%) rename {cortix/docs => docs}/src_rst/cortix_main.rst (100%) rename {cortix/docs => docs}/src_rst/module.rst (100%) rename {cortix/docs => docs}/src_rst/modules.rst (100%) rename {cortix/docs => docs}/src_rst/network.rst (100%) rename {cortix/docs => docs}/src_rst/node.rst (100%) rename {cortix/docs => docs}/src_rst/port.rst (100%) rename {cortix/docs => docs}/support_rst/modules.rst (100%) rename {cortix/docs => docs}/support_rst/nuclear_rst/actor.rst (100%) rename {cortix/docs => docs}/support_rst/nuclear_rst/fuel_bucket.rst (100%) rename {cortix/docs => docs}/support_rst/nuclear_rst/fuel_bundle.rst (100%) rename {cortix/docs => docs}/support_rst/nuclear_rst/fuel_segment.rst (100%) rename {cortix/docs => docs}/support_rst/nuclear_rst/fuelsegmentsgroups.rst (100%) rename {cortix/docs => docs}/support_rst/nuclear_rst/fuelslug.rst (100%) rename {cortix/docs => docs}/support_rst/nuclear_rst/modules.rst (100%) rename {cortix/docs => docs}/support_rst/nuclear_rst/nuclides.rst (100%) rename {cortix/docs => docs}/support_rst/periodictable.rst (100%) rename {cortix/docs => docs}/support_rst/phase.rst (100%) rename {cortix/docs => docs}/support_rst/phase_new.rst (100%) rename {cortix/docs => docs}/support_rst/phase_newest.rst (100%) rename {cortix/docs => docs}/support_rst/quantity.rst (100%) rename {cortix/docs => docs}/support_rst/specie.rst (100%) rename {cortix/docs => docs}/support_rst/species.rst (100%) rename {cortix/docs => docs}/support_rst/stream.rst (100%) diff --git a/cortix/docs/_templates/globaltoc.html b/cortix/docs/_templates/globaltoc.html deleted file mode 100644 index 238fa4a8..00000000 --- a/cortix/docs/_templates/globaltoc.html +++ /dev/null @@ -1,46 +0,0 @@ - -Cortix -

About Cortix

-

-Cortix is a Python library for network modeling and HPC simulation. -

-

-Cortix Group
-c/o UMass Innovation Hub
-110 Canal St., 3rd Floor
-Lower, MA 01852 -

- -

-Prof. V. F. de Almeida, Ph.D.
-Group Leader
-Dept. of Chemical Engineering (Nuclear Program)
-UMass Lowell
-

- - - -

{{ ('Table of Contents') }}

- - -

Cortix Links

- - -

HPC Resources

- diff --git a/cortix/docs/cortix-cover.png b/cortix/docs/cortix-cover.png deleted file mode 100644 index 0fac59ca2684ef2db0093a87c636097b701ba682..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 276517 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 z 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⁣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

xrBp(^9x*0^>5AIn*m{E>v$MsF_-6pCJ$Mm zt*sZQFV9&_w5!dnpA7wWi)G3n|NkI{%}_vFKoAj#Ul2y98X+YGlz~kpUWAHqec=G| zo{>o501Xt=3s3{#E77F=+dUWz#nDoE$yHDR-M0`bxLrJ=+WGlGWCK|71}WFdtJC(-E1P zO1_UU=JQ!!Li*;yJEkdK39`Fj;e6MT;!x!cCEGjbu(eKk3ItwYVaZ>$61j}?Or?PU zxmcg2l3ezxXl1)e?pQwT=PDS)sSOBWtb%?dZM0z!&2A=TD^KKcu0z1Z)aM)@O zzB-6xH=9@1W3{L+Q7<#j^y&qL1N0@*MI1LF_#sqN?INX{J<^j#TZq*RC06+9&c*^1r*}4^(JV>;V&1~Oy_%JGhK9)Y^SVfUufPv zg%W9g0H%N?YE4qKbDg{4g1Xd1FvSaUB_>b5;tCKr_fIdK8-%fRy1~yLmY5u z;b=hYM`r_KH9Fz^Yd_YW;SUgNg>XKYsTw%*;@FBaLe&Zi5*3Fb16wykx+Rgn!Ka@o zRG-K3R5QMB4}AHAZ!xvKdp*MKD6|k*IPw~*UJeJKC_LHA2)%)s8vSAd1VH6L} zP1cwi8QkgZy??U<`|wAJ8xe3WeQM-^CTmONi0T5!w3PyYCC4l0P@8OHtX_~$5jT2w zCgt-j_RhfEWJreX5UMsuei75PsSWV@RGaloWElzCJP3#jK-K7EVn%{+Zy7fBL>&uy z@|IIJEzyTGFWsza`dp9$SMAu25#bLOT zFTHyUvB5U&eq|f)v~58Dgb|6LB#;@v;!oq)bDb9;i!YKgBJjkWOtCAI)|Au&eTei+ z!ABrP%^&@5@tU9%#w%A*r+J>v#gj*nWp+>iOC({O3qipuLpd6sl5B^isJgAN;@Z%O z|DH4UM`3Qdux7M|YC%=>f||LnAFEW@7F8Z_^Hr19oA-B$4TbmE1W`}|GenP( zgmFhP&Z)NmqO=M9fKiDHR>lp3iJvEBwpbswhT0J@i%3CHGwIJ&$wq`W<~rAQQwTPe zNU?fhHwT|fvc{*o1j>8ZAtQU0uTBt%m453 zNMAb0;m-GdFyIR17`bDY4gdwd#PpqoFuL^E!~(*PLebvd@uvAW@c8imd%W3~UN5>@ zap3A)5;+%H_5(6IDN!TBGD0=Zu1#7qR+~CRsdJqWf%IVGdi1G3C9K{bkSfss0dMo2 zSf~#{9zfUzBZ+?B{|5Z=S0tevvf5c`&H6OESW4aPt&O+YeKf|u9WdCeXUD)hbLQY3 z*M8c@464eu#-muaZ48)TUyupR6vrk2R{`E;y1jCpZ##4T+eMtyrIO*T%_nPp(S^o@ ztowiwYd8lQxjNKLxcmv9MKT=gN4IA=E(|7i3-l;CHrEgBR7W%&R2UpL5@(?@Hj$4G z&cskyHmzfgBr0tcSK#1nz;zVUeOqo}OQ~T6{q6o{Vn;ROIWRtNLShDJ+f-5gpm`b= zjR-1nC*#RkwlQgGcH@*f|Luoi836k2$Q}&tBtcNW!^4jGEOsTC6%5!xud4iwBoNUj z!3~m-;n`(VHrux1YQsMgdfrQb$H>F*iT-DHWfWBz90egE=(AXkh{?x1+ZPMW4&-jP z*T85&(p$@bEBVo{Y_DH^lAt2yTg((MO93-E7yvWnaF0Ow za{7}hcD&m0t@v8G%R*|n zisR26i#cR~N=E~}Cjk`B=@2#W405P&9Nd*rz^=Sdd{S|ovjsn8JgLBPLWWmtJSCL5c#VjK5 z6#VNW#1O*A)8PSMw~YvvF-h@265J7WiFT96GU)o$DokHKXeOotOkWv_^abRFaMgHX zMm+)?vI+}ZIcM`HvMdnIA2*Xkev5EdAPNC}%Z1v%0%7%U7((N+5t6VM0jQmL;>MB_ z9p*C#;1C}i_s;C_maE!uPEZPk1FoUfo+?yZ0`|?h4cfDzH6)=rDPT8a3?w)-`7$wN zZ64VG$4u=?3V1SeG;W3;DgcZAaD8qU32#Ocv~ye1c#34esUTL13&hpr?)9n2fc{lS zeE>uXOp76^vVr3RHZ~sv;3pyUg(PB&)iDht4Y<+TEpp9Lz9 z`+!LnR*=Fy%O-+t*ZDdOXU5wxFo8xWYsJ~WZS+noJ&u>p#}KyChsBq#CX%h-UorqZ z4IBm7SPVuJzIqo;Xg)@~h`WVcilQvDp9R)qi4d3XF~MXj1qiV#R9 zPf2SErjc!tqblHY9UQJ8^8qUg?|-JS76}gmG%+x2+64+xD)|%m5vy>4KR33R&K%71-_vJv)BR;@}vI%8^&hl(g!RHREHu!&au23N5xcFES%{vx&Gi%sIdq-P#(lSW;smC z!AKun$0K z6%niuV)My=hDRWQwbJ9X7Df5$8-wdH6?#f+u*zL`>AMii$9?k9!EZ24Atj)t{~p6D zym}3-8LLpOj2po+40nc<+R7r8SwXIT9l;|nJ?wMc%G3N>YCNMk++Q$5>HQ_Yukt}n z%gTtm^i9O3J-%2N!I^`pPlvP#4i$(5tS8Efp_Oy6T&WKQARDtG$Wd>PqRrZ%|CoLq zm=!>E{IF(fsjk#=e4LzdLUW;Hpko?F=SR^Sp)MR2TOf>L2Z=tEekHTB#?r~*O(6!4`l zf&VBQAVGf}FWqbmU(Keddh((nDZ-H?D1>YqN(!dMa5>aDt%PpqID4^Oc|@~!xF9)J zh5k9A?ovmz?}Tz`+DcC}76Z z$q#Lx<|mWCVfw&nvb4+#KqnEHKtHs}Q-vMDUa1V!Sd zk9^^KM~C4iqE&UZIKojP3_5-SJJb%3j*sRQZy7Ux%6!@CrQ#n;G@f!wtydg3381K! z(v5UM$0HyRtskr4g9Sa#!MnWRfJEXK%+zIoZ{Iha+(@7dhHdK_fLSHn+*Jh^mJ8YN zrg&3PO=(q~S*Z4(zFkQVU)L{JZwx-~CoORDbO?g3uENZaI{SIh$$3YyN+WwAV)%R( zL78EVbm0=%3tBMSe4;Z*)8&k*Q$;A?ExR_y7N!rdN)P9XG-CAan>4*Ojf|*id#UrE<;9#f^;V2L>zk%*GY8wFK z*|su4*6%RA&GsHyR4s$JskTf}YAGn08d*U(8vOmw8H4xod-vRvdHna2@V_Rb_CLAy z;@mm@U77cPMql6-y0Yg=X7u0td*WZ|OzajCLSkOR4xD9djBq}wfh&x+EBH84g4JBw zyb);|%Jq1BElP$yQyTPZpod4$!tZ#D!emd@w5VRnxAuO@H!?0xOh*`jUdzP?!+Q4n z*V;|~<}P;RcmYSCe>H0~{xPIw6OYcUU`ub+3F8JY1iyzn1-jC;#trUPEOR*J9T@7e92Yd1N`UF^0szNSXw z-$R-gL{bwLOl&gn=Y!a|wkANZML&yYO@*kPjbI4a3^zW`oW4mkJ>c2?&^B>Agm%F) zZ0bO^=XCm}!7U*mYP$VcVZ>~>lW@!K%D;uWazx{yNy9rM^0m8^cLgiTT4PSdvP4F| zU%qXd$@ zoaftZah&_KOB8tgJ@1pKFA*wQuWJ9v$f4p+04Z35I;+VUYq5S@Dqpb0YIPqzF7x1s z$<5+N7e?LcMjcm&{rWFhGJDhnM#5|BZ?L2%*^enS+!AiR3hUNv{CrUZJZ^AEqmj~Z zL%3B0#%DivpY=Ov)L&`;)BOnLq=Ra)12J6f{q?#9=nn^7)> zb>*MISUCV;&B3x0&YM5D>N9$BYk0{gQTgF>@#ljM^$5w%<(=9R9w_Hx@i!qpv8|mx zC*$-JPN}DF{`5`F&?G$13h;S)>{DWNyY2TkV`07@n+vQ*s~ppFrGU1b*%-TzCghnN zs1y=<^y|4M7_b)X#4d@q1HM|F;j^-|tU!Yu9og#b7?3WV=pz^>sxM(#L7iwn@pBPB zA=jM^%A?uz=p&r9xzglJGbY zubZ)K3fh9Q`hTCr-=Eey1uUQyu7Hk;rgI%gJX*5=8|0nWLnuc7x< z78F}%x;P}+^S-k7S_LZ}`2JR1fr&n3I+Bqq>tx`g=5n%zDR1U7TV}d;U+{xm3h=WE z8CqLZ&*91=rdb-3FSr-Lf^Vtdz-o=+qfC!gawk^UP4*@Q*ewB7v9^-qZBMQTb8JAZ0NUoU8rEY6S@Gg(xF*W9^n3$L+ zoxs31&&gUpiQ;g&VA5{=?yio8a(t3J;V)e0xV@G_V6p0U^a80xmd4ez4GTXGyj5-iL6b8^aP{dO-#s8o2wAd1kDzLb zz1l|$4yUQ&rE+U>we>nWR{~p-v!*Gq$rpLjQ;M`P=yXIh4vyHcbVxhGtqo%&oZ&kF zf*ha282RQsAVAKCx!u<1%xLhUP=~tf>H<4rC4dP1OG{~PZ5mitmP@W=Xqs)TVirNV z@8>=~kL$fwhibwiT9$t@Ph7b3hx((#PQHf`2FXrY#on3mYC4*lnm{O@CIv^Y{&bJg zr0jGj%qF{Idh4TP*KgVK7WKn+U}}z&u=D#|k?$P%hm>t1Z~h79cFGbOwy~K*+e`kq zg9)yAbZtvZ=zZmo(@(LSK8;HPdsuLC{Tfh7>J?tg2VY`TYf3iCdz_;ofo?9Vw=Q8t zco7DqWe^yY*N40kssA^;Cyw@yZo3R#(F@_csa&Q{A>mJ zr?zxp{EztAY^LJsQ^TA5m(jl*C<0;f;2t}%?JT-vKHfQ9(%+5psNS^hSdUhzr!H+d z%8MkPhz=N6!g-bRLVg%l)Xq9BR1T}NE^(Eaufo^V{-=>WtWb1(m3-4hYB^$Iri?kL zN12XL7=Il%i|UJ*c3(Zh=T_}C$_R6tQ#q#W?>WlgwCfKIQ0lS>$~SI)4lhdrkxG}a z)ZL^NK9}Nuxb=L6Yv)q8h1W&3x(W1-Jd(Tlc6Y>|SCJR$DrCts2twxG^*v{3S8aDI z*^ND0dcVCzaF__M85y}|AP5B?bN;t(949VB^*W^I6Qtj(I+gc2jDV}y$8&>1QZ)rv zl{{*fjB`&fiIw7I4&~;RpZoHNBX{;`hX-x1@v*AiL6l`*O66=!U;FX>SMds>$svBS zF)FJo#+onSWpBMuG2)i8jFUd|pX?KIPU+4i`gWN?G1e@o>|&B~fVW{#ux;UnSxB3H z$;zM6CMkL#+M|+I?`XIlT<1MzRRk_VFw@9OOQQcu0Dp3_iH?eo$c-rRK+mQoC3O9< z?F8t-obQcVd}5iZx8pioKhOXE5-{>i{w9CsY+lv!&3JI9U)biVSg+=sTTjv3{1_=4 zabOQyHt}}S^cMNa05yT+HThNTH@LP)dZF!K`R~^&Vm{qx#s07n4rjM}@o)~;Hl4in zYvYOPJWMsAmY^Uv9j@pBh*cnV)% zjC`mN?jm$M9v@s+cNdB#h8n@3)ID~iWKk(8z~R5dHAw`pVl57AMkx+Z<=H13(rr0;&F;PccCwZiWs^Kx-ze7~mP5D! zNBqRrthIeHvlWAbMjPqOc~L#J)eFlip7l@Sww$Y5 zqjB(`r0DqsSi#dMUm03{AQ~u{Lz%ny?|GLuGyIP`+VEeTA>hUOp3A&$o0$Q7uS#xE z=e*s^CEh)C%x#p9UGUpa!S#Ra85|(;>8h3@>=~2wF)Qudkn`oo07L+fmkS;6mnpq# zIX-dY;`_-Eet!NG)WTkswSdk3NdB+2D3T!y^6AOY&JVpr+vl>4|M~-<8K-}qL{)|J zMF?xZvf>QRUxPpz^nJk+_}NC=!P6$>F0A|b&}lCFo}yd%F`XX1ckdQ02Tsp#9BLos zBPqV2ZlLw$Qye^zsAV8jmK)?QZu{<}Y`i7ueEbunr1gj@JbwRf~ zmw*%SDtTae;zW+Dg}j0zP9$=|B*e^0Z8a|Onu7=#DRldcCx;Y3MdA$!GCnt#a<-&` zJ5?XFjFPJaxwz%H;@vasf?oUk=RUI02Ylr{YBsMzj&+}%YmCPwuRWO7VbXs*-PG2X zQd1utQeQ4FPn@5I9BUt!0KRWJ|BF}l$&NRdm)EqxjA2s%Gv95q@ag!9)|N!7zwTP4 zN+@}6NEVE&xfHr!HX|66@o)FJ#w!i=K3qxyz>qyDiMv>(=#Tlwe`bnaX}q5l5Tg47 zh`H|`E&QTZ*HbptWUgkewB_3k zWdQ7S3F%8j*-?h2>ykU{+AD&Jcd9RYVSWce^_vaDs-ycCbk)l1Xt zjJ%#=J4cEHnFQh#= zVQlwer~1;JI?-=oJKEx=SWI;j4W?_nvIxw{nhskYN%#KRAyFs=>O^Pxq*eAI7va{f zJ#{APCxJVc|H-Y*W@0!$oZ;>P<8vI__%8*#|GZPZU^-WdN3^xm_wzj@n6r5hV`w&>P0Tzd+g?DPbnmxfCM0f$bF^6BHaiGDsp+>kR z3ubOVmjAyg3>?mud~4ETIp=$ki?YQfK8qhtpchqg3(C(i!f1TD-Sz8p3B4iB#BX?3 zZkJo+7Wz&G!LV(NqWIRl-HCxSy%Aw6*_AI|JDi)3dB`jS|MwLSlfHOQ79` z$`)Oc;_x5F0%hKvOZ8QgyzL6KnXL>Nmt+``AHlBu(if%{9W^?I0DJk%dP z>o+*ewmmk-Y^Nx~Xm|H`=@Rml!%v7s9zOng6&P4fM>g;o7xIA?LRWk>HVw_ zFC8ceW;~d?V5#Octl>B4rzxVJX2FVo?lLrP8!;QL$0b~_1kJ`FR`IsP#a(>S^DeHW z9%kK+|F3F<|G)8p^EI{X9ju2P60f<=;j~r`f*i7Ib+KBvn?&$E+APo;?2`qgKPE$y zZ%6}Hv#I_N={Csv`GogPA=-+TiV+$^{>oqw&Jnc({_On&!_SqrlgI0FGX6=kFa7|8{&lg>ZM{SB_z?JvP-p>vu9+P9IgjzEZ(`g4<&F* zJE}n0V(qCstC!F6Ed%w7H^j6*KcezaZ{`%9t6{hwiZM_I$W+JaNerkvjZk1r;%=ynA z32(nQwtVuyXUOKL%0UgiS+ezAW!GV zycfCqjNha>%jd2m2VyN-zo2X%R8l&ocn2CTf#-HZm|X(9{A3GO#3jjJX{>8FF5Du= z#<{o75-R!LcSS{Yu#wK1`6(5JIQ$O*<#2AhAJTntc`Sb9( z4@;$NzH^zW%$iaJ*2v7O{CN#xT22NIebOb=Z>~+xlo0g>7PCp;fBd+?LafFzKHeIx z2L^Wi<@PG&P~|xx)mKR#TUl6@Tl%Oczm)aklCurQNvr>`Y!)(`9OH-XGL8@tU?7-~ zG*dqwy1t0k4p@pexyhexjh$GJzO+`E(HlUwJbvNZ?^y%0@l+a{h+aw$K#G$k+F|at z_G&-zzq*89S=_4{Pr|NAx!DwM(8nGDoSGiWY|fgRtDNQ3Q(V@l3+SmhrzZkZwys0n zU);&ND|Y+vp65G|!WCRxZUYo{@p06thS7#dtyiJqvL39m%uQIxc#q#%vt=57U7J7P z{kbcR&EZ_TX{fNjUauH7G~(ACt^^^(xw)g+7_ZJVkPd^UnxM8`Ok>5mUz`%_pY}do z9aI3-Bd=A^5^{NCO?7>N9!Azw!@ehYy$>Cq%uaLneNeuXob2x8yC?oTzOf}<6V>(n zy~<%wR!JXCZN`X&yzI7eb3aF4&=hP1Rdu?aS_eR~A~Li>?qYsVjz9Lcmd8XX$-%( zGYoSYAoAA*|MH0OcbETs4(+J{U(3L?D}wvi~sM{qX5E9p|W(7yQcQLP3KpL zR4Cp%7`XVNp4QSjDtZkSBLG0;G6Vv=T9Vr6X930WFtvjCLY@>{`WeLO983GLvW5!n zSE+w+yAU8Kp2N$%pldUZwl~?u4*lZ2Uzrwe$uNS7ntvI1`9ca;igt)@JglO-vf`I{ ziz@TJ?pILWl>;f=Alh;2OAK?_OZTG3ou(|Eq|bx|>)xqj==Y%?TwF~Ief11ds@%$% zeTUSMil1(jGec{CPP4PA#YSCmZUGB6N{Z_F^HRB8uc=Kx9uEP9 zJ_`*zKR2I7V;Z;DZH@IaLIp8V$wI?@QI(g3+Mh<9K7Bhk%Cwr0Bv#^#TcX>K@s+_} z1XBL7$+o)GJXR7aA@t~@qPpPq&MBfdDzWB z7FjeaBAb1sG2zaIKmMvs^idsp5@mW?R+Iph$~ght#pmWv|E={>h11nJBNFgOQO6GE zak%c!)3mL&OzIX88nz#jzhG5S(1FLvnkv~{4I4bjU3_MK6KyZ|GDrVOR6zLYpvg<3 z#>?v5#jt0|gpfZ?MTD#}7f#k$JE;En=~;VT=nAMl#HG!{ToJ|Y6RG>~VlW1>2CSM2(7=vDsil)W=I&80x*5xLU% zXM<$8nfA-teXi)FL0wby1w-y)c#%@k>1TX@EHzp*@Dc?jH5z*x4tU6~q2vQYqOXpO zeT(uJs1#Una8T|3Qkr|+@8sfWvrGKSNbgDD=2~0lpF?t)-bMTn@NC3l)1~cD1 ztm5Y6_l9$(mvUE9`Nht5lLp(v4U}R|dXDUdV5s zg!qkeZH1wr_Yw3&b4 zvjaYCqzvg#{%__XquKoQINl)4XW&M2kC9oiiFLq3?NEQ|=Ct5o`yi{ZSzkG%@x(Lv z!J+SLTn>xpG9a3$ToGVY%(oW}-;EpENJ3))>L8L`Znz z+T#|}X1{SwN6codLC^2E{so}RgBhEYkvW;ci}d631U-ya8+AHgVstLT)7%_ zZ?>YjPItdqpmhY^hHt|ey$s&kY55qx3z~(*`U@2 z%d&DSsPj=1u$DnH9}5S){L`^U5yiE4W1hutwDvW8+dZAd`6f&2j9M}EEj~E&6&xh0 zs|R)SW?Yy(@%!@XnX}xWHW`4DcaCWKaEy6wmD7rc`=+?Q>Vlnc&}5jU?`k~A9hp!x zL%*MN22At!H@)rYk|akyJsBs zQ>8Mr?gq)A-yfVM$VMK$HAB_)gxBx59!KrHqhliR1@gU{5T+wd#IHpm;zY_6|1}5K z!j0$lj`unOa*#=imaO@v9v1Ta$kl?LsWS%k9~*+z{i&1^d39HPv0$pZz$POZBCnj) zEp40Q3(Op~c+3>ZIp<#QNGAU*$;o*r3Z==N$9f@$8g#RrVQkFB!157#Uhs z)XK?u_Jf0;{xapcbYPyH6a0Ew==PzZ2WWfC201p@cTeFtZ=y_3RPwG|&n?9_ei2s< zBZxS=o-wqpI8wwvf5gVgrr-%Z?7VDl)pNtL=qQ+~q33G2Q{Maak6J8$GCsQ)S_n7; zqkkRB!RnF~33LkBgkSQysfzJIO(e_e0tm4??E|eP&UWm|ZezwbObq~ny0c!3Z#7Fm z04^EUI=w}U+;AA=<_?0Xow-r24+RIc9I*t5RlL%u8KE71=EgF-$jL#D9u4?=^XMqT z9i6=7Y=YUrJHED~t_^eAT?02Y|HwI)r0}9?t}zt$2X`rqCNtvFrWDQ|{&sInQB_`; z8MkTCP+!pW;h)X;E34y7wO`zT&1-wPQzK*Mzgr8EK>#>o>1b=;A-)umsX+O~(=#m= zbOFiiGWRG3eVv6!C1zvMtt;&Mz}h0t%XUOZA;T@TUvB&@MCrg&Q6r$XId@iwH(OfQ zwd+rNo^V|m+9Xs&ua!ovT=Ha-(#$RW>#n{SR|mc`qSHL-GR7R0F&Za(lZRXIt*N1K z1HTGq=HO!SpDyh9ZaG=WeG=}zVC#&oy*>1GA0hQ$V5K12)=xuj3bogeDRwq}P8F#T zy#<=@%t6MbspqH9LCyDl;VNfPm%n=sO+_VQvy{P;Fpb9jCpsH>&)kqZ($kmx&o%U* zwnp&&l_LZ>7xxYAGk`c9 zx&qq&1~y;OnGA<9U#NYuRhhK)m=feUC8y+;e(zV!Wu&E&^VzL77C zYR$^mMbxjleV;leE*~(!p;NQIV%(fqKzA#X`|`@Ph#-B_{qroHHYG@L25l;^PR*uR zagu4(09EjImziV!skVj-%-uAfF0OWm={7ae|M)xuIs*sPuZh9xZ%a_c^Qxh zGtm7j9wJhDwQ+0ccRXTqt$2AC-FGc_<%wY*hNwzYc&Mm;zk0AB=m*4;PECY=Qa4Y1 zx1{*w!t;vuU?tZ^i|M1arwkc^YmM40w#}ekE`VxB!H(BKZG0sVHj3P+NxHqa#wC4z ztg316Z$pORe7=!W}5rh&2{+2T6k0?eC_WiUXi$`#k(KUHvdTOoD|?l3_R2V$hC(4{-q{>w zXYWxN#UW>$brjj0WE>9nd-M79{k{G(?>(Nc$MgAoJRh&;^FY+YF%tQy4It>I(7=Ph zQ1S630mEx@l*R~cBZH|te6aH#Jad+2qSS^iH3fgtZ*Ow9y@KN3*|5eANxjYY+e~K~ zIMhHz^2qUPATcctN6%L>D$DgwHj^W5LuZV0q1)UsH^vioTnphoSPYVumPSw6lo3cj zfGTCdvDASBsXfoD`R5*+_ZBg{@cq4}%}8l^pQd~JNbLghPuureQG&Y|{Pl*}1i zj6VKq!3H(Q2%}pfQaFvdjk%2fNn!Fkzsv983O}m+6+%_a7<9$>oS%Gjs@egkA4}cE zT}8N@!SM9`YU8{g4R}D1dKps-P*slwf4qQlF)(B8OW~#{1yav=_=JGj1 zdMzvv5(c3?W$th+sqd#yU{Li*bs5Mjb6Lu1JeYL0h>WVL?U|5Gl7z}}{JtXaIR5;~ zd#VXr_};y606^aYaf_r#-2waKWR6+TBy6Lz+iaQf{IPFAgkotJ3msPYA)75xJ2!cG zXlXWayNy`Y+@H=Ww;}F9c&<$9vos?h>3n^JiqgT^T|;=E$IT~7#agpu$7Z4ngnV!E zOxi!@DAXXlCWJ4rS2>r;Erf$f%%J z*Ou^Q>^#=jv*J})e|NX3J%*vl`z$QlG*VRZr$RDOCR(_8Z+X6{qn4lr8|w@r#j?Hi zhV=`28!gPhTapXB<D`0VxX0v|jaHJ4O! zCmlc_Bb2|ZQX1nGosSrrIw+4E64gI^3DBNYqat=0JsiGODBj+@c+BorCmJ|F+1NaF z0qD2Cl%=_eWjElSl675%wBeum6i&5HJb~tq42$v@W^W+&sH49MtE8Mop&S7L23&k=th1wti!V!H)mPN2)XU^ynF5%&)Qa zD#7TbG`qgD99n9PI0xP}$9UqM$KK4O51LBCjKby~dsltn1?qr4$VI^hx$M-I!_HlP z?xP~9%Z6oR4cnA81A%`auhekLhH5ss&Jtt0mD_FF1l#ISck5#y4gDtEqco!t%jl)) zC1H-(=@5P%Lr_ijhIx%SbFEoI=Yl`;k98JL`rlu$L6xt`eL5ts4Zb$KgQlfuz?b&g ztDv;WX8aW>mpu9V!L0qAXgpS%3dq$_ZQ?#5u^Kp#p||zirq2QB+6nLuT>#vI9G|u* z<=*NEd8D#lv-&z|c7i|X{n9?bz{CSgOnE_BIRJX!&(a>5TOU4ei)|De0%#O;0TKS{ zjUZxRVGiXVq!kD3n>sM1`PwJ-A6* zp=Z@fEO1DsP9AjlRWO5i5L|NCGx5AL*>r~z5z)ajbB|@?+V5)~9MOW_22uts6X)CZ zSUmNWgmpJffMBQV15tc{GYQm(5wEU!$|BCo(?{o)HQ;B2Uw0#~k| zS{c?I_2(Kzn9`UIZt(c7x`h;*lh~FssamrJpaksQSl{((!QZ!{`FaYuEoI;tKOy(yK7PE9ozNS7^Rl2-_wA!+OHx6u`FY~!1?J+}YyHjo*n;EYML^rstE2Q5R7yK9pzACD(MC2BaIg;H=bd{FCTdNzFPcm7K`Lh| z#ZF6>`Sl|-09E9b)+zaa}+p@ST{%P-uTVW(VJflm1+4x-FCXI zgfJ*xw4?aAh>N)KROui>#cQ4%n6}vLLyb^r?{XfuLeduQF_e1!4;cV%`=$k?N4WCi zK~xX^oyt0x=gz`sajXQ%9FT8yZ}5AOOAhB{MSzfp&UQY&%@-;fBfR`O;)B`M-ryyZ74mUfn8o{7s3Ba1HA%ob;0B>cb|l z$YeDxEn>LbDmi{H(a(TZ8CDf-qU;irPR9s+B${^-;qLy1#Q^mP|r=mG}rX*=E>CPVckUbJpeJC-$$u~)9F`+b}E8$)N zygYzLv?t7prl|JX=R#<{WJpaRQzO5o;lPiOyU`@7Wl2sC3#CW<&7v@xw7I=;WnWS{v#M7b6l#J>BDG3nsyC?AC1C}^vXeO*en zdEi_v>#eGbeM=0!F%is~>&!Q1UbLCAXBh4Dr8OG*?Mzsn<|=+NoewB=AA5hA3*P9< zC+jweSJh(a4rF*<88mw;3DXr@Y_~KQFH21auIy3zIQT@2_;{pwtlc^K#ms8%^sTnB zvy_J_lh-T<@y1)t}g74{=(;4eW6j*NdABV>s*n>bO`_e~WGS>5?IC^MtFN;34y`4H&xW8gr zBVH3z)-j4rGyKxhyML~Ovx+>9(F!Z8mTvPF)}BQ~8<5qQ0(WZuxR3W6-;oddd*^3(s~)5B`K!GE!C8hsFpUyafj8Y;>N1fA5^c#=+wW_wAC#C{ETWzDf^h^{OQWZ08fOGQ5z5_n!JWEU3L_SQ#D&|A_bMJM>O)?UY0DQc0_Z zRrwL)QE_*F2U}K=t=3hmHvj_E0SFLWST4*1r@P?p6LWc#pC<}Rm7u#_)it%#g$?>J zBRjFQ*IE^^c&d}yxk|dnJB^ivbIT+4=%B$*O$m!zK8GCYXHPfWvxYzX8%Qt7dK2!} z6i9Olnc4T%&)Z^^7g%)XIoc`P+ZHX|oBgu#o8dr=OSrvx`GKoN`;g165Z$tc!kIl8*qi+w4lCJ77+ zlYNZhaj56OefS_DSIrl=+Gszx-7im)30yVpB6|TF63Bc- z5>g*rZtGwfIH+?4ZgO3DmZ&37-F@(2iT&{=HmKy7=A_Cx;Jg+M&y;|KvFoI? zMUql_xEK!Uw4eWFd1oQ8t<`z5wJ-H9w31}bvXP;`dwWyvN3!gjE$&{Za=Kc5s{3$a z?n7mM!=q~EBkcaAXc)!-tx_Eu$0RiQ2HV6?m)$KJ_^qhTzJYtt;NImF(=RRSTd~+x zeZ*#Tgcy}PExg2Ur7&|dYv5F{3iEPo-W0xcsbJZw+(enIhEtFT{p8dC?5{3ly#N5I zM8l(7Kvh-(qkCDED-haKb|*KLy?@lFbr&ozv;O%+{y;S|(bfwd>1R0b4!B4SNTaEK z>U3*iU_iMfs1BeaFwcHo;LvYF58R)vUUB(R# z$vV${=wAtRa}S`y`(69G+bVP9gn526^6|-+JI5k&uSD%XBWAr#R%hte0R1S`N7YxQ7_cxIe9dR=6z zul?>IGKYn#w!5}vK5GRbY~k9e6Hf6c`KX9Dj!-46_AK?}7|L_@+j3jq>Da~U&I;h1 z$L=E~VOnAJ8w(q~F3L9OiRN%=84C`=&l3FGnaYP%}Qq}rW)?c9Re))z#-A(Ll&s~_>$P|kKDoZb( z9cuhCt7{*B(7?|hBFl3&eFBYQs~K6IFEB-r&Ed<% z{?9uXelD<-vw1Mig%C|YstST(Cn|mbbGF-oELPlgyb9-&E9YcmXC?mUi;(ANZS0Sa zFU|tm>0=fjP^j!~bGv)e1G0{r!jQ~25n{Nya~~I(S*9Z*seQ0>V9{?1wfYXj{bxeax5+X>cnu2PLj*xn0!mw)aA-kw? zX)crxr`+zV?Bu8^xH^ETBnr+_)*qGJ^JX>&xi7WlH!wVK8Sz2df^Y;Zhktf zmEDAs#m+4T&)-71Vf}=of1(nqdj$EBirO=V*xS#a`q3V-E9Y_jcRgRt7!S_{EY=>4 zeFtr62?d@Lv{JRU0BYG4vv?Gk`0cj?iRrREour`g&Jl8t7e*;^P^5V29Lh(X$TE=o z%_qv}V|DAD$N=kg_th`_BgV~3`xEZX>H0D9^~a>Am8%X7Wh4CA~ z&FUrR=)@u*5##f4Pro0)!|8^_&O$cpurgv0ik=;i)_~_S9LYoS<_b?X)4@Li-2XZU~xO zh!>?oE75%=MD}i#(zhXaUmWkDYC&Y;U(;GsFKC&pfv7;d zD}n`x^MN|tt&qvUsXBw>nokT}h=KKC6BI4fqgett+e7R*gC>=ojU z+P0g6=8N0lZOmcsd1j&_E)DlwMqglg@hk^1=HjDhj-o}XhDp#%J{PsHo_1_Ay1h9> z?R-(Je0}u1^R(mLti@CnpOC|-5ZC;HgXwBfWzL?46FO=?`2nkEqpX2h)KWym>97uZ zQTJ^xGoeIBxpZS^r1cKnh@goV_+tQk%FrhTE5mZ9G!iEg5a5g0JW|(6IAYuNgHK5e zCPEPMI$Pw=p%e|hem2))@4xv@pd;LRPE}g|IsOw#_KjHi9sW#D^}%i6_!E4=74fTA zif%dD2n^GMG`xqo(#h!NxCB2g3pnPpjwOkisvzwFr%a?!8N0pX#9oj~wOJunii)qnvs&CQN~9 z2K5Eex~93z+z4ZqJNdR_OKpY{QsG<=Y@YjE=8lfY*lF^{rQ}!)L63J`2R;AT!_(6A zrEA-?&M+s)=0)>`_WyL5h(JK6Td<5 z;*psvkU=YStYN+%%{PD4o@Qk8n~<|yy;yGiM|a!-Ti5OA#A+z5O8aUXeF^qEO>mxg zbskU74IUMx_9lWBRv8dx1=>UE1HP86VYibPD7ERQ7)8pD!myM4koUvv{~UQKehv<+ zRZr-p4f^<6HF=LK^z!H(i1y-7)VienEzFN`vXGn^l9uDI`cK07pl$;il4w?;JiFua`exaz;Pe)~5{rhsY2MxpcL^Yr|%?EKm-C~ksd*KH^i+J%#T6dafhNewh z0FmA&_Uwl986W}$PF7T+>bZU!Jbu8Ci)EfX&%#-K>kBr)5$mArH8jx;1+lwLy@|LR z#%$2+UYnCSk)i1+9MCz4+Ov(f$6^JdD4L^q4k72gmgP4*)&&u})=F0{v&ctBY_M!R zP-=qB@;Q}#Px07NQPPZz0FJ(Wi>MRh3AxPjUbTISrQA_Tn7P<;X18V1c#r<~58z zr6&ZTJ<4BQsrZbG=|@{$k1HRof&r(e=f0Go?k#c42uL8PCg2bR1&F|5DF25_lmA?y za)H@Ac;ueSzH6csBm-G@{j>+suM(e)!v>X)@Inl$s2pz2<;%z9`{_Ua8R<%pO4;}L zG^ZsY?2}opmu}BCl$px-CK&uoxXVRu-q^5&)LU)#Mp2_xwx(70}+VT z^8H{(7M3&CrhS^nsxS^0i5aGEEOtYHy)Jw#3ceUDIWcl^^IWPZg&Y2lW_ofA+Jl!fMj_p!oxt^X8%rkQx)5~W;9iz(_ zo#@5eoc<){+Ou9nxEuv=YQW^ls+`c;MzaAQF-*a2*XveNjwae0sU!4pWk0aR4|Hw( zuh=k51~t^?e5>;e5?%TS;r{hj|L3=fTfuGndGTCdv4-!e;Mh5nj9Jez$~N-zbQLuy zMT}V0%{|`=AN<=ECv4*kpP}zsXH|ES^$&>`Reb>~e5Ikg&t7}4rv&P0o&tfo@4Klh zBVeA>k8F^{Ta|MV2B5ptZF~5FAbOWQSwp&$Fx@Wzw6`d)BMT$A*NCn0RY{mBBr##g zTz_#S-Wv|NDpabOFOlDil@z~*# zh&*a2iS( za2hT&n2~+BjzG5@Ew8ZY1C0cq{vh_lI5>1FqA)C%JAg(Emm!q|UJEW`$*%elEf{; zwibpcavf9vjY*P*m?;n7wXbxHq>bwJQIv02PNT-A!LmG#@edXpm4F+yc9a%{i_9EI zRStvKE_m$?5|50N{H;^2eB9}hiP8VP(2q4ts8YEO0V!NI5ua}eZ9O~i-@AOL5Aik6 zX((KF6Xd_2)ZjX$wXh{G=6Q&{3>jKTv5a!VoB@H({kj5Mr!AiD#|FUJf!2ApU$7%d zTm0e8VhfJ2!%EmGkV3%Z;XOoaKY?5yC`F!k5V*Bqx4cGMLo=X2|k ze72V{XHZZW%7kB8>Y2LFbXpy1`YEaYS>S7~S)k(@>h~qBGmb>Tn$GTbo6EgeJziV5 zYwzj>SeZbBC424zi+eK}$*crf3`=POTTlC!{H;d^nfK}JF7xJdQ;$6CnZ4uLzmdrs zoYLiaAulgY5u`((^7l?3^^T&Id@NALV>4;NpYDza_4KmhTlfE1Nvw{)d!`TcH9$7v z)uqh`UWV7X-Kz9~L;;4Z2Zyim$_hJ=OXEV(oJaX zhknahWA`H9k(NET)igEeiqmmhNQxbtPmq{OJ%#;k|CH|wwZi8A%(&oWry6I!Fr@S@ zU&h=|x+x?i@uzfRxYVMtJ6L2a;9|9rw8CoytpW%9s<zKV#hlv@=Fz_*;b^LA~u(Os&*qM~|JZyAt&Fv9w;%!y{?nL+(@gLxQ424x}VqF?t&I2zl z2sKuUq&=Nvu|#CHjRW4>9@gb9WMT~a5a@>kH+*+brGeaqtciz;WVw8MJM)qrHDdgA zK35Zs0gjkYGp4<~Kq^nazBC&ae36ahiOOB&h>_Gg*sOOzvwE3!?@@s9k7<~=sA&g7 zM*5A>j$pSH-tg({I%rkGro7v12Co+Orjj{)#AJh0h?3Ej9rbh1Om*Wu{6dEW8_2Y- zE;>>3zYcT8I=!Qwk;@G@U{exgoa&O+&yi^RD9(%TIJd@y%JPrrZ6wejC=d14!;tDQ z1ZoU-SsCa-at1YqMiO;L$sQT;-XW5aKuIdE53ART}t#{k&;gCmQBF_{=v(m2_bIK5K&9a%;JeLvq3tY!PR zTi$c0PQJ!(a08$FZmVzEO6qV?4W8{Hkt>`W9LG4k?>LYB>us1dIM{V6USU!zU^D<` z-KwlXa>Z4dD~v55&w7zwx#&@Wx-Ci_|9HZTU``WXk&0j+_dZPqzuS-3Vo`Y!->^3m z300j{9j>7LK0@o6QK|SyD|k%}rVi1fIpksuFM=(n%!ymmqxei+q9r^tDYcW$l7qZj zw<@RA8m#n+UYZ@`EWQ#92KyupO%bF#Nz?UY?#aWilN912ahTYW(TGSGdWg$Mo=x)B zb&IW~jv>Q8H$|exo<{L4b?El#Z_Q*-)*HyHn(@Np>Qv6NmaLE`060qX%>OB4leo=) zw{Iq@C1&>PpDX>gXej7$Hn8=HR+|*&=>N6`vXkGTc(v}SYDrUfH-Kth6J+MVl@DC1 z{G}A&{G)d#?EU)_%ZbzH4d(cGqz1VMEdoY0UK>P6O)Ioh**h89Ko4zXn_cABhb~6 zeFeBffW2bNSW+}>_AI}F%fu8fJz+jJz!RBVNRsg^?(NQgvl=|kOC6x!VY_3 zExd;GNeYjcz<}8wN15F)Ro@)QM~=6fbD|+cn_B|}e*2GQLo(5cDCkIv$vs#q+igWI z020Jt&AhJc#t>axcKuR(Z9N&ww)xUluzl0=cUkm>EO0u-i)MtFX%fYkG8TtC`?kL6 zKJKdjmC_&mnW$<*PtD#_VNY2|_Pu43&2C4b-~aR3-BXC^koT}@ZTiv`Q;Wp=9Bkc4 z4Us$+M2w!Y@}JP#_x9>S_K&Ql-=_e*Q~{g_Rm9~{f!DwUNBBW0GboH&ys{T;!EWpu z-8h}947~3P9`9zf9F}e2J+@c#StjU;MP9)(=2Hq&UTHpsPqJN-+N>pJcn3I6cA^23 z-hA2Rpe_YV6vtoqa!a$HcT;ryt;vNt>KZS8Nkb}oC^iIXXN{R-PWvxp{x$fI) zEY5=XP#F5cM_(hzN`eV8{8ptCOzL#PTTfE|pF6gb3uX5QhQ@`xldAl%)!}llL0N3= zuEhss_=_Q9z)b+F+Y;#r=tPxm?7oc!G`e~Nhs*g(GbMe{F)IW79ZCS=zhy*j)rM_T z_?K4SRsb!?l)p-3Px3pKhgDqUO;6S00fCNs?o&BSbqg!sz!sNa7_3Dp$~Y^?TUMjV z&I+-~VD)_11rfJCT9^&Z4wEDOTf!Cif3}_jkDp{JXy`D{Fwl=4dVprV$b;D<&I-SP zX1~YG*_5W|I;h))A~NHj?G#VGXgxFed`1$~ z%sr}b9>}o%T!MD7l`(-u*uB;+o{25jf!Cy< zkt-Q(;AGfwNC$V%zg=0)>l0Cb5HDQGCIo@NQoGFMMhKi0Sk^Bu+qPe$#?~RQKSK5S z-3wq5P8~@sp|yK9rAP1x|2U~^+OsTMbg7ro3#3q=^}h$<>Hcc^rSI^t;V`{W={vym z$F1LpSQw?tt7XyJDA}51$W?ES^qw;MD>8ZOw7J`0;x+=ER z7KXIqU0VR=jR3)H+w+<@TdI{t2@&?$jB;|9^z(1MwDy$y$`T z_cCJK&_VyPm*&Ru`HzyGH2q~F-*xuNF)T>w4&h)t!Tx>XjbooNqy0?y*IsMJjFgV- zPhy#Uik%cBP!Xp{Wf$(yebzDM2pCO&_rlmG?5mV92KY`2Djg0lsgT>@DdsN-##VRS za=Zo8-wB2%9k_iFPcd3ff&?x?fl+bCi&s3v ze0TkFVPVm2wddC5pHw)PD?ZVj$5a0Fh%NiuGWVuRiFG^zcFC1GvIUaJAYfvJt3FB0 z7``?yLAQKrlN(<@WTpaLE2tZ+o_7{m(uVs!bjz__#=3YZGjXfZCJd_uNWPJI_5e+x zzgu2mx-rds1r2f2|9X#jq(Mt=z?4&wMIEZcs!Le8W~VtEKbYU}cw_C*XR4!dmr~1Y zOjf%~VF1Zv?1>08fUoGCYxdh81CwSrAnU3JwAvkyyJ0uX&3ih%HINuJ=EAfytc=4#;4sK-XMJMO7OOWtn|s z1nIo`>jeJUo@*DA*na){8I?s*quo2&j$ga++Cw|{*51{%z+Iwgt&-Mzp#aRIq#q8X z*Uv6H!^yVCxttoj27@7ktIr5tqIcJhzfB~OxF-UI>PfZzffR$;C z-}!hTgCJE^{JQRDqm_=-V$jgwW8#I6=;D?g*Hc+Z<%eGh_OwJM+ejALo7Xv5SicBc&-ZBRbSd4{Z-2A4&kPcYK~*PKJ;lytitTcTUx{Nn zF(@66*)Be^tQ4z|MkZ=$+Zox8c6dZqrhPLsXci(CK2r2=<{DX=O&Z@9^qQ!jS%eKN6vc#3=TK2G1yyDp=0;*mBW;`0;6s6>K-jc;fIw`kJ2|YH4n3v@dtXo1 zzD_4zX^3nPK!5N{!Sr3@87<5(ATyR4mIR@0$+zUd?MKM-%UZ4 zU=}?mBuFf^OZTHJ_4}A)rk3~n_gZt1=@hdrbNscXdFl)bLxcVR6Zt{4A@uszKafTh z91W0DR@^6|y{a(!U>~)2_mP$c&s9tzW(EV>e(cI++dZ&-3+x~NW437tnXj?0hWnz` zBq@5cXC%svue=mzB)tnI!0Xun~~%yZ+i0TJMtwrcxdt#60^=G5q6-VyV-vpnS`v; zB-U{XIo&DL^V^(xp0BnXr`n8lfxz%W`}d&9VBWRV1ui^lj2-AWs{010t;6twPUC7b zA5R}q6d)@rL}n3N*RK{g8h^?0I(|eEt`{UXN^E`-FK3f`ynIi6%GDVJU zTrKHQ-XD1i@VD8(IL1z1&%W@}+X+>%C|SWJL72Px{*#a!2BpuQDl2P^6$v`sno=3? zpOC!YqU>1I$=$euUA>LqPW!Vl@61{kl7-py(&ytg*h1ej!LcDI->?_-xArTma#Zj> zU{_v8MP)5A=S}}j&s9E6h?&N~UTZ3jvTvS3pXYTny-2kGRTIN5El*h;E5ZDOD zKRFJS$%0IM1AoLc`-*_>0D+867SdAQH||Mn68 z*m@4_TvA|{Y$o{vDxgBg74R-2X!VS&NB?2uvOHxAl8)HF%Mq{=EyxcfXLebw7z?&$ zafuiLc9j>wo%94~F5ex6a^)4=dcAK+Jo3K)#OKB@-|^MmxydFWwuUff1o^N(xOqB#!SbS@zs+1w`cPB<_8nL&f;8Iu8(ve7u?O32 z&-1WXaoBPAVdRQ?t7}3=$V>qn>%Z))PZTlbP(xwN0AAyHoZ1Ko!E0V($n26R zxZhV>pgFZ(m47zPuTS9gA4F|FLL1gFZY#_bu0QwutjlUV-1jBUwJ^;c1`CvYDx-h2 zFqF-(7bFL$c&c8bI{Y(iBy2?JnQbV6w*GDK~Y4ioKphx0sEJJ=;0 z+4uc3`+3)r2d20&bw(rcx>l}E_uZmq{{3ZXfoZ16Lm%aM*2;GsUWW}B3qo+fBsIkB zAucmfOMea+$a5&Ck|Y}C)}GK|Np3GQ!K^&FmEztKPVfMy=ZT6yg2pk#qsaYQ{eY<3Rajvhai8JqDBDLmGvG|3&`* zMf>|W_vvWwuBbYEcyIBgK608d;1A~8GM@bM-xnSi=;ip8vt3$)BSjaO;Me^eb(OEc zNa33>j1FMWk9o`Q3EK=;uRY@iU;RPM)f-(2W7`p+jV5H5n-181o0oyN=z}>4c5rmNUDlT7J1ug~D`G zb8&;-*}xN{D2KQHaxlD=JIGJP)TI__{>IUzLo%*gi1Z3_o)_?J&z zV*`QMgbu!b+#q@Lr@6Abu+WbaH&9(A`$BiZ7Z>$?_Hk91n8_pQ|_WGGCuc+OlSKhTHlb zaDxePKaW5FB?6dWA3`Q!F38Lf#mGwgFK5MWpeCQh6<#9dpgK_b!SBzJ$62d&vS0}dAMe3 zPi^@hL5Zo4^#T`#LS9&N%&Tz#v4$7;>QAC)s*_d!reF9xGGJlQWsSJWd$sVhgi#2e zAm*}%?lSMyd19sBc$qFSBm*N{fe8{g4dA-(JG%OTN%<~f76NDb=il+YPTtC(2TK}Y}T1Bqf*eQ0^BYI zQlj@47J!Zi@<&RQAx^7^!8e8m1d$g|2gir^$lk8a1j)!1%0?~TXDw0j-@@ZpR20XZ z8R<4`Oz%x#O;{yaMr&VTvzk@ka^nNPB%<9P(qYq4sI#PFWDbm34v%^rn zpC}U{*xux)t+Oz4N4nu%E4Rb)sFt%8TL@S%uTO%vHWxWa%+VgyH+}h#}cPyc+I|5Y0mcIskn2+>6wYbqQ9EzB!?8sxmOt#D5{}d}3;tZ#{}2 zuyjpXOYNRop{97B3ry^$)5}2NPg~`WiAQ;;l9CVQuFT54u2;b#iQDBgz539fPDqRx zn83eu9g$i2fd?VqgXN0%PQx&<9TE&5HIb&N|LSVLvQ4Gbu1+3of*jt`&2! z){z+U0!1kKaG!Jv_Qu{L1s26syTB`(k%~#7M`Sv|In|f57Z0pVIzo>^a~&F%gH5#g z()$VW@2VP6t|g29a%xL*_)1>c>r0l({hs6T1z=_UJH0Lz06DvsOOct@pmUQjqWHTW zXyORS492~T4g&Y>N~U9A^*US#jJ*@P@*Gcb=am&$dNwc34a#c&%fy~@noI@U*Ug(Z zhfsWXeifI`b2RL~a`rwb4`v<44-XeC#K~^eqVdB(6C3X@zzavt6K8u@AHa+{giZ90 z!x0OwCKNj;EUEK2;-Bx|p|T^@1&-*)f))9q{n#0YTZ9wa=t*$w{w=6=JdB2JXvU9%V;S{fOv7En`fN%k5nE9(trOK1!& z{s6>vI(Ixdh2-5=& zMDY?G0to*2r?){M9okoF;}!#t;_lQW3|ZkiP+)3M)*Q!&-FOtn0jFPaPXyp3UOr`| zb?*)SiXDe77dtEyD(Yi*qVi4q61Mb-p%m~E4GfssEEW3nnzS zr2b$v+}3G1KJ@?%99?!iudW~QCD5yKTZ*o?~4`LmoN?q6M z$wuK{Ed=WEFXE6YXp?YEFIzKLv7n=mvXqB&(T4&zU*asCGOJ%}!d5sYw!YqMqlfVJ zr!TBF6@bqb!G;TN9~|g^=|yF%)5GLNe{YB>*6lEDKXlUNHUoiz6Xp(zZU9 zAw4(GgG8&W&(F^$%}|l({4!Xc1kh+2m4;ZK|6U)uD?ZB2$c&iJCJnl<{Dma{eRZtz z#fn3*l1bHdbCjQ9U68h|53&78aU~%f*c;4K(;}u z(fhPhc{8NI)qKmY?UAoPZ=4?vC9NT(=XT?_x&qhL?_LHnBp+f(s2gr%KwM)a!8;dY zQ=xGhq_6?0R0oJEQ@O0w>xfptT($o`Hf(n!rk+?d`Es738)yA*diJg@?XDO7)$CPX z$6}Y^!bHinCP!LFotw00FBbzfcYdG%wH~NLA_Tif0kv6$%S-%AZ^E`!7trEVAOZVi z^?ld2{YYd0ob_br9PmNs1l(-q>>0>3e8)6zLf9+XCGt0)@EBdT<(1~|EI{dQa@=%V zeu0{)?oZSMRYA_#t{8^WF^}2ks`m35KtP?B<2q5pApx%fIyBPtrs5a1`q171t@_n# z>otRgMJ4E`z{n__?#V-b$UQxr71*Ftn9~i;*+|Bo@OKGV7kk*k>4YJebUUDRU;|Vy zC+pm+lh92~KYzrrTJ$Fq4=8NLlVE@^tfI#Sij5vf?2FEUD6fKJsvf?S*c}Cu|K41O|ia;<5qw(|v*7xgiP}I?h1=t3IQZ}8P zmzb3bK)>_vQj7REPF2`koUGQ!zL5|vB*tC*EEy}PxT#Q5^WO?J5G`JJG{l~dhvKlv&{=AbLeJ9{Oga#}-Y95n3{(L) zkAK&<|J;iM7eJ(((-%tp)}~SRJ*$WIg0#vv4*43sx_qCNk#XBfEdUesssWvu6QDOc zkH?RU6fVSdIT0u81@i%WnQJ4+$9q3j_-2Z%Yh(ar{X&hogDuYM=!q@eLS&+kD`F>0 z;rub%&2A(5zWls^;Py7IsXk~3e{mlL{8T`wsH6p5GaAtIpi6+$$XrIj$imYu+a>|S z%PJ%_$7m@i&(_?#dw zmxn5QE9U+7Ie=^=`m0n91Jb3F<=LD6Ik;cZiQ>04_YPX581gdCwB4K1k{)zMjl# zXlQt#dei@6Cf@4!dVzC&^jGtEn>I)8MfOdUo4Pvu)?q0q^h3XR z^Hyx_)OS6Nt|b6XKoNCh{IOc+r?`eB(mNY-G?oW=ziWN(s_fhL*TGkph#AMV{gb5t zh(qdQj?FqO$tgB?=g~xst3=?=DgYsZ8e)}i_nGVJzKe%laE=5geFswV5B-r{OO@Jk z2CgDsG%w7%hX!lmE$=EZF){hCdHoz)C<699e|pGM^>5vHpgZsRVe#_wd!zQ0U>{pj zE_6Po5rEsf8ye~Ue=av{$FTg%0{)PMy;dKWo8R~9UA>P86>0>a70L~n(8B@sIa-)% z9r@QWZ}IQ?g9pzyXkXlZed1l%%6-?y3l=<^p{wKB;W2u8wC4WxC-wkbblG5G`Vhd0 z0Wdk_JLYQSS}~S?Tml=$;}@#&{`P6PQBm~ieon0kxWxD9a*0n8z~Ptyl~k7mz$)T_ zwiyFOViW_bhX=QH4+)`Ha%D+l8sybh_Y8gS|X?7s+8U$4UukAl_G>-%NhW{ zz9ylnlfBC@9n{^T2vV!<|G@HH3}6~>nzCJ#>F)zK^Dg_oV(Ab~EXAaV0^fe1>bN0Y zG7huPKqSSi4OV{F1trAMFw3glAYRFfiaH2ACBINr2?boZAm*kOj6+Cjt*OAYdS>d; z*M6YrpGd0CvdUzIE|FpXzvYMIvoPv7ljG`M*4=1il}g*b5wAs(7C)H#9e9MKk|A#^qjd-wZ9MG6k}@k)@qJz-5W?!&kt*-N^5m-PZ%-FZma#f{pud# zJ|Sx&>W{1;fujU@>@8Lh2qj(_g4|&iK=CYjAj(d(vL|_R=rmno;ZV$GJ@UW6r5-H84AFsN2|Fl92bQ|D)xx;agdc2pyEw6pr2 zRB0*cF%QQteVm!C8@Z^b1Bou8av>g`?-sdoIAzK8-PTl!kM0%wG8GUA_lwcy!K#{; zuxRZo*)E}|diGa9p<3?gBmD^m>u71oD?%d=Dj9WkF6RC!5TQ9*00~}kZitOFKX-5^{;v;qoiBOsct`6bU{_x@K2TuFU;pwEL}b(lf;e(^ zFr-b*rQQFGG+XZuD^K;$aRzZOeP~f(LLq4$jF<-{Ls3dj6HVS^Qh3^eBBwrm7QG>j zQFp$v1g4~B3zV&51E6kkCcQZjJZsgSf=N+W@2GTN(YyzWq2|x5@Jne$fM8?jmjfI% zO|0^XGOdMK6mQGb{qHOSkjfjh4-Fz%KfKbLpuD2ta)!OJ0rBZVe-QlZbF;EK_U22g zpfv!{8@k*rm1Zl;YQ)4Hcb0kY3BJtAW@xRC+ZmJrewN);dS`}ich4A8t~p9||C^Gaxnp&KL-opy9nPAh-|#Y`Tum6Byd|#d*DsY3Use1y^aM z_FJ2wJ_%x43ntV0JOT06-ED~^KUpg6hG+>($58fIgp$Z9mW;Y^F->TWi#W0gM+27& zlkW7J9V&5|qb#n4AkEC)X#fHAk!!;`DLY`xBiVI4+ugf;1XwtY2XiI}gG@fFXSq18 z^FT10n$(tPX?{-mHGd(Q)sLBr}^ug<;GcUQZv&qnt>vkEVUZa8cd2tZn4%1|7UM#C;2 ze0h;8(PipR!>7xS%hqDHrUvY7x;7$(oa8@<)Wqb1$zi~!Fz)Sw2e2sU`{CUKX5CFr zj_H#EX-7O?fkj7G7rdv8N`=eVI$mAyHux>n01XK!9+Cs@qGRmVeOi1yXd!rJc6PRR zHp^^ngc!;J$#wbR((f+C#K_w}!kPSE+qxB!1B z|7^68Qqd=jg$lt&|M={0%}j!cVaI{3boEVr{sxH9f+AHh!2)l9V?w&b9AIs?1DTRE z%eyoLKXPa=(Qrp#fosUAN$#SVD>t6Wt)+S|XSYdzGlJ~wS7O-^vm_{YaigxwHwmEZ zB8u%ZYJd>oQ_DK4hx?8Bf8%O)fNH0VbKxMP(2QTtX}q#;O^P)?I8(E@3o67Hy(iTQ zJg0Z1GhgQv_k=Rh8Wr_)DI=5D2(7HAmqzk&?peB|Vx{pdjgqh)K{cslN|i=6xT6CX zIT|%*W(lN0KpD%{lIM>Gge?{o26X5YaVh1_=7IaQFifd6ICH)G6Y|JZm72GARhZ6;)E>k)|9EoYmnvT-LKJ-V#(7ic(8{pYaHac< z&(}_mxwi^hioMINaZ<9TUhD3^5M)36eBnx%`0jTanm#ok7~b(Fv2>BooXMpPXAMMV`l#GYn;bNVLn#*>|I;kWWD!@|M*7 z9S1=}4oLEO{bhVSx)XKBBm~GG`2M(Xjdre3#)$t}qedJ2pV zaVef#{pU2Xx4zN*7O}IYbhnGi8roL4jBoXsk}L8nW{AJ3&ojy%tZ&e<@$*qSAS5%G z0f3N!QGMATZ9)+|QSK+i^80q2Q>Kn@+sc*(D>t$+nBE6urgyZdo|j%|^E4D^cdr5g zk^d}3R(0sJmWIaL2)Chp5Ph$K{?61newblTm%>ZLPjgz^eA+r|N#La{aAOMsUaqW} z==Y&|LF#`{1IzoCUzD~~S|+}ITTuSJx%qp3AufbXE8t7Ewiv4Z4c4mFT2oEMUIF02 zu~}ik(N~LsfK^t{*cUu*dAa!7tq*~dE%A{iS3$~I9`ycz);gp_h!VPyCJKXXBYfeP z_8vLKR!B$vjbcS4MyDZdw>^%9j9&mv$3Y{n`gHM~B*EojtF|OD@u#VW)|yRC+N8Bl zszXL)tjD4I?ii!@96u2#iGhlEY3Gb{XW5Px)itvb4Z5g<<-~dN1_>iQ|(gdxk68~9X%{1QZt)XRQI zW5s+~1vtkLT?)#&>fUV5?<22)3ah!z7G*-E?;8_ezw-h}B;LpM^P*HwUENggD*GQx z7KB7lb#Y%B0_TRV-2(mvJ2BQ^Ej`m2$lU36oI$QR9@Mb1ffLxhnxi(GR9|StsbJ{I zoY?|hSy}&pGZkV5-aaY9%D!K{O(oaoz7ZW0F`7zxj5hc%W$nIbywbV1&t#9xrqh~s zO{Mw()V0Qwr6Ywrm+ns`Av$3WB!${#6_9XYQ4w)j9?lK0%l^p)DZT+`z}nuo1hQk^ zriU>tk~xG`aHa?JI%djmT63=^eDUFRp~y zcNeZ>uaT@-UYp}C==IP7RE9PaI=XGFr(ffEJ`(R(nL30WF}`_2V<(g(*i+(ZFS)xN zDG(wt2lA^-pR%}qR}6pOhDcH1;Ew+(!2>X*q>yQ@`XFp82W zX9lYPPYQQ3i@DBAauxix@fzkQ*Yel`UUx{zC&JlzI=S(8+Tb||QS7Y7sXp7m`Q=^? z0GUb}uw%&I+S<}y_gK%ig3=_VUji#?I(-cG`kG*OhoOBBzhuQ!RUbIeSG>XN`3M}b zc3;#{1P6;Sz&RO>`mmHT*aLyJNrxgnus)9AZ{sUlv;G^yYWyI&0JL~F1^wla(nplD zqx=)FIQCKKiGzgH*##ELaX`uns?%9oqQo1FRH0BGG*w%~rw(9AXp&8l9JA55%A{tK zy=}6zX8+N>*|DR$*WLI)526?yPr{!SfzED#IfjxnoF)vvjVN6*c)*riZgAI(9pUHF zXR4-WBne6k06F)pG%_bhh%9t)ft$j+?!)bm=HY5v2+aW2d?OTZU<$7|8dZ7KS|6I{ z{&<1SKwCV_bAs2{KYe_j@u*A@JsGbrx!!Rlf!|yHZCdfwXbd77LQ;zC`LL!Rz2APD z*uDw`e*0Pg*xlLr<2)rA@4iZIf089WiA@GMgpil2rRb0M+ACPp0SC)KpGEP<<`!qr zn?fEbJp{gs=&7>zq5;qKc!1YFk`g7aR`5f@;9|OCh#5<^iC_ zZu|9|GEGqa0Usvkc6Nf00SIk?y5p_!qIS?2-UT0veNaBLg>)W$d-S~c3H2zDR<)*& zu_K$mbG--y+iZ6J{CNhct((F^Lest?2~nxA2IpmiX9_B25c40W7Kq^7f~G1c;+3K+fZY5Nsx8dm zLmJuyC2t;k0-VW&1o_D*;^C;|?+d1X!FKhhpt0p->#w{FKkGMlYrP|_e0uPtb74-%O$%s$8&KU1Rm8kG^IGh)da!- z52<_(9N|NN!=TqmC_fY&750T=U#JWcP|)M^PE24v%5J^q{nvJI)etHnXg_>Q(s|Dx z3Pk{fsAz6hdc`1!ATt&F4tuJ^-@5BLSDGlbyOeegWdT0L3J$^Q+{=?YDQx}~i`Ws# z<;=$+SCc*F#u_MOfl_h>j)OGmhdR{yxG!%R21D=)8X9%c!7+!LJ<2592)`&Bzke+) zFDfD~O)l?QEwdx`m z`xZu`%CO4!-UF^UnuD`zhYNC56sn8Q$v7BSI6oIS-}ucfV9|$8R13%J*}ixR7IgK= zlP3u~%O{nTmAg?dLb})T4w27f$*0?Rtjz0N^J(qnJzu^U87@VIEHLMy(!DHh9Caly z11G8_d`(9Sfk5cDPxoZ_E5-6)l=&)pCIE9peZPvmH^w==ZZjAkcM*fllze;;CYO2% zWq0g>PXN|6UXTBm!T1Gf437<}5s2PnUa+Iw&yG262`&En)MBrlyrT+xr@KC0lKc5J z*2hZjd3eI42C=_!8!K)YT-)q8Vc}!-lb6?x3P%HDy8X2_m_gPp-;rL_ zYi;gbn;gj1E`G`%dtzt}PA8&!fmc(%%yDY@&5_a@3pwhl$rUc0Un9(FiPFwXuf>ZW zXTGq%?pOxEo`0gT>!th|uh7v{dM}k{-Z~w91Q1&OjQ`LLvikizB>r&6-xeQ{o>W)n zR=4AKYK-?F%#ZFtsDOEMvvmB2t(_47bC6J6S{zk37TJ51a*`LUc8V7{d$Y7vMivGq z?!;H*VTsF6rjK$f594j4gM4-ZPxZVK89BGU+dN@!gLmp4ItlFknH9#E-P-jSw;G*U z{&L0<7_~|$KP}wDq;13y9=TcwrfQ|}dp#A$SMklK$9%wIQgUI?*?0kquB5|GI%%%0 z&eF({aI$uqrtti(YspEAh5o8pL#EAn#YWGPVclkbhSA#QB(WmPY9VEV59Y_o^lHwt z7-I?7a_~)h*h3jN_)(@d%SfT&x(hIwRk|zL4#!r~@~{tZjK5p+AZC2rmx*nw#>MAk zu_w@ifZ;bCDduF^YW(vhb5@@|cPuVG-)iS)U3mFan-YVdd(tv(WSV6dNV1Y#XN&IDyCZUh&?vmua?Rdc}Xc+Kv;L&sdo^Ww-(RS^5VPL1DIKQ>xa7Kzfb(-WO}2P!uIah>4#^{SCUcx6XFu)kcof#`@J4S={ZOdz*xKGcyo~I5 z&aaeX+R)v8fQ{7IAZ}g%VZy^|Xsd8tnz$Mvf67~4?bP(FBR$Y+6!wd|$xNySu6%pN zkIchzQv86`?4Aj{TUKA}N_ISUy;uSMKXbZ2;YlDCJ4iqGkCr*t9KnCPLMeY$;>zWy zpq}k`!nJ)IPY|7`u28{B-#crEpu=qGClvGSqXHByXDuucv z{D92$1U6C^_2f|mvc%*3pUqXAU~IEhY!7rO{5R;1ygY4#@pte7>c;y5Ka-(Hki+B| z6xj@;4%@_im6;{vtx0flRK9nPw@dLlDRQ9J>F~+(J#la|ux%@a>sp$cJvjy^t8f#t z^$iUbMgS1_uMttvOD71|vwe8umShaGv^5Aru7K28pPT2|lPwCmVkzdPGV=ky+Z zkufZD_C7l&in4}54-ASdd$-dINDIy9cT|JvPnyIpx*dEd_wDGS-j4j~+ToGSRw~KP zs_>Azpkz6+8o63j*WZHT@c4QV7Q~8=3vpifsFbOy=+THsoYD9Lya)`jTUjxy!HG#J zXU6ZdQ|`sjYxwao;&r9^1h?*z1Utvv&#jBLWaq>Y`k=GlGVZBA`*QMmm9$y25C-wS ztNt?US@C#vW&9~H(6$VH*_rI&GKf%;y`+<-GZ$J8^aDNI{ zrKC&>csz~WbgoXhDc$>8`6P1Wc+go8%DSA2GMngV)&+?rk8)*?o+*Po^}iMb`2)H{ zR-fK1v}r+u3dVYn&P+*phOV~x1ae-xz=QGt;E%JdB<0YHUF6%0zfQ-Z(9fU$Q&sKt zzB#6%dok)$5n(IYk502}M$(4-h+Md_u~%uDObl4wpn+eK@4t+&2#s8b-Ku;of}m9 z=i>Zuij678X`Ep?(T>sEss~0h=dRw@Ss^BPOwQ{s8u>_ zc25i1tLiuouH3qQEff(l)BO?T1^J@Gx4Kf$P(lO!1yf@3c0lQg|4_BJLh;-yTOTXf zUY6nF0-|hN#nx-Du5f*1WGMw7#+cUN>EcAmauHb9xBQmrXd5cM6k$A@Au=3txTO^S zjCAzNOL?~}Fhe9@KKp~UmflQ2vAlas^jB)}+p{bG;VA4rN`({j+8||)I2HbTmd^|y zn!s-r(>&-EsLCkKOb`rMWkFx-&6&wCv~w&27yG9t4fRpEdHnvmCR~=yWx4nmY?RBa zB^@OnQ20TmtB85x*9~Fs>RM5bqDd|j_brZyF#mdVR`GKni(kP9Ec?uwCW|@A^CaHv zBXwsQ=YhX)d#z#K?8+T|Yx__xlFk{hL3Es4v331Ia7-SSi+TO`l`osR2RfM=BF~im zc=p@&VGmpCcvv(rXySuK-_1>0hK|fs&)Z-_rUT%_d54>6=q5bfw+ClnpRVBJ5GaPg zLV+4YMSm_cOr}S4)(yI}Ur^+v8_e%yeo~#7o?UK~=&zI6u+M0?V8&JZ>dLXj@Z_vD z;{_HI2BnW+)e` zK_HrLgUjC*tHLZRAm{B$q@sS$U*?pjN6WGEya=|rolU;>Ze#(k!+U5|K z5y~|WF8PTGA14a$p4euJsuOqn3b0!^3QJH*m~(^H#xH2;Et(U$NsXAKK8a`>{qHaP zg!#qAyng+L-pP^+807=$G-O~KapRj5Z7u*h{3N+a7XypryUfi_`DPx6MPLHaiFe`_ z?J$y~P#h6AKn5z>aF9wzAcOentLK$BSnAuN|$pum30fmmaXe!94yPD{H+Wsn_(X6#8OH+b9uICR{m(i>t!FKM5tTSxVubtnX*RUk8K07K&Oj?yZ3)Z ze6SzkH<<>qm9RMv7VU>V=*S*Djh4IU4S*qyd50=&X;HRzRNp%}i4)0F@3+`k&`WV`He>5+& z@r(lao+xMEW85@(*u=`2->Fd!Bf)Ore8Ww%OTo_fcTfd0zq@;IQ+T&5_`zakdV01_ z(FJjzxj0H{06B0`F@dJGS;nko0!lZ#@%$XOyP~Ld*Ofe=2(mw9oVh{jNyDz^4Y0KR zG1R>@le#UBHwKX)jn56Ir5Jmb>VpR5U|t}4gt5&sR)`47`cKjqFOyzUPfF+`!#)4j zBsL)1RDG)t1QCA!7N6W-CEP%~$iE7Dm5?G6_2qGM+7Muz)CR~6?&5(K270p`4`AY= z(q=95C~*)g+Y#gdC=^kK-?T+&)3xm4x*w-agg`m@`=S$#@mk%cN!j2V)qDI8GjB3E z-`_hx@mCp@h*kKp6`EK4@lXWY24#gRRBp6?*Ml2Za{tbYa%C@3AH?I7BUazi9{3a4 zmr9z4j7^f6&rIAw;lzJ_x$W|(zl{>js@sgby%b|K9n(4bI|9PCbHT?vZ-c3x z^4$9zOk>lV_d-)k%NPl+S!0HgWXpCt#tp~E5rHwu z66mDBUNxoj&!5YRc9DLxT{C@u=PNl;Y7l)^HuVX`mwN8OSpsfI57T{HQU@ zQ1UOJw66HKa<9<eW`c$&kl_$2ArZ7ntwsx93g1+wf(8(l7n~UrWs}HJM@tl zG!-hz#Kl-&lV$NGjMeg0bkxAu;{-Ipq$r9DO^4^}e#vYypnKz4*ecny=lkOyWw4&92;_!4eeECTymWDIP;P?nwe;K$DKgV$Eiu>VI(V?# zV2>_mD^P-6I*1&8bOQQa(V*NdBrtD)Xth76uL?7^VU~*T{8>)w0HZP|>*l;{0sQlw z&~+Z-%+lLBO85lnp)F!HtnSGb>(D?k81++pTq2tbC^=HF>!<%(XU&7qGSw~}s)kt4 zxXZzR9=ldM9if9IedV3oN%h?_NG7^EQ}$i^D4dJR>dQqlhNtw0U!?Y9;-6{0rjnIE)!W-=LIYll{32v+ImF5M*JJM(b@fo|%F~$% zK$-HkMX2?4(gL+^xgl}``o;b@mc&3crE7rHlH@u2$2QTj05K3+N$-Cd5^|DEKO`=h zUo|rCLJ7GAE{G-kSf5F(6 zg6hG8pt?U~mb4?H`fHNT|Cy;=$bz>=mUDmCgGJm%TLpu(O%kOq_2WJudu;Fm$nI*&yPy8p5h;e>OujWvQ2F@r;}4|GIwB9P z^%HFO6X$zEN<@#~^XQxSxPKvk_X|Zu^^VQi!LLYy25x`+qjfsa>5C-hC(WNo=WuuI z-QCsIxU&xyQz_;4$BsYc_CpLfqE=P+BUIc?{}^mZB$Qu=t@kAGC{Wn`4fdn__`$2$ zZ|Z)WH~F|t|L5^bGu&q{!w(3SPrVcYy7h{{Q2wri_PJL$8BqNf&7Qt(#K+WF)`b<;gF(pmu*=WslKG z3^asL6v}G)x!>tJ9$3WAhFt>@`5myNRP?h;dH0G5K4X7drZ=JdV(dc`4qh)}dG=oY zo+$0tK2x7`pnTj)KJYY0SJPIqE64p$ncYiCOl$;VDw`xo^;*8$={&H}qz9;^-SVe0 zZ<%U;i>F5Sw4={W-EUL!=>8Ui8~tUcpAO%&*fuwgE_jW&X`5ePz|$4{tzk#?;H@7m zac8d?p&h=GS0<}-`lk5yUALtO<>MflsL&M&8C%Z{{2&KclzpvxEyD9N=xz3g%p0&e z8xh!bT3~G_fA64|!}pRa2_hRh!nR-K^d@=G0l}wN$Ma~Nmty^WG7sd#L60ps*`kRL zrLXgmk6lkLUTpB*k3xlnl&Bug=h1EWxXX|&3f;id!2y-z9TCpfx||Q%0Lq}&_T9cm zB-J!en0!n+^XI;u#G+j`)%t&Z@MmVf5^s~Q%CScjBG4CIGA+|GFJ z_0{2Wj8|@cAW#s@gXZ~Y6)G!1Wd;)PemVaPRI_*z7A0=5EAu;)9&6`8ElVUBk6AQs z{ zJUA4X1#aac8!_8M{*mJ0TjvQP&WglohwEAy_YyX{9$S2g;hD2qon1p$?=EvwvZcB{ zawJkn`gwFG=vF}}k}Kq#*iNhohtEv>4aPrhM3W4yF;$~{pMPUatV(nJRSGl>_d2T1 z=VUcn=;fH@t|Q(*E>lewpKxPXD#$NeK%DlmRQyWjR7=tRlF{n2K4-AsORb`uiDQqs z;Ah-HZ*jH(YIFLib&nV#eb@g+)yMcaBUG~}qv?XhmyzF5RpuO2j#b@QaTmV#_zm~U z1fG(9z`2+B$NEvGk4iUC6HY51($Xe4(9* zcxQPpJ|lH~=R0^%T0EVKw@Ktb<9wb^xc{M>OtZB(G(g9^;k--5`|tB0kYY8g#``>&6%$ z^`8Dsg;Hjr5!ypzrS{U1zgspL83QEN{hQ_5;tpEL#Ir~#`f4_avV9GS(lJ=V;;qkx zTdx?he)w+$R^iA4Lo@x@KuZ3Ru~gb>8GBqwY4K&)rmtm}*7Xabn4#Kpe^6hvi;QX? zn{x#57oKFw0!rm~&#_7pp3RFVwnN&Ri&0H+or|YN(wWBymqDA-1O>bT+_q9X6-)NP zSX%Y@a=21nV%G9`?yJ0EEHq=a41(5q!=6*dJ3vhIU>*kyqW4}-Is%S0<^|clvjvf8 z6H4N+j;@Q&z{mX{5W_3y4#K4A%$B)}6TK7+k#118u|I<7K{>?qdi8zmrM>4OlK=>m z7Oyrdx+z@UxOi$iB+6f5TNV6G4NJg<2-$WyCEO&587Iv?)G5%Bg|E#3ftpU#>(`@h z%nxP4lEj2TQJUkT_Nvfceg11s)xuX3g!{^b3xs6J2_olh%vvs@Y{ck9P+IxVrM?L78o$_n;mxS}X z)txMHSpdl0Ob+nNRVUfGcvL!lB_H(_%L>B~D<};cx&nPJuxKKbA5@TsV6+MR8F}c8 zOL?ospeFq5ZWlxOH`#_3_ES&5pVXrtd@)}SG#J}$q6OeH0W+`Czw}h!H9TK7r=(Nx zw16W#F$+{nYZ9jq2;ZO9y7r`Q?ZG0SnvZNzX1gOLJ3qs&bHm$T6bjVR(Zu86NByWF zSTNy@Q^~UUkE{20tpQ?2c`rZ;)-^xy54Z7E!?dmKdWI5$^yG{5Zw&p@VM2zP;k@Ke z|H+orTzM4+ZiTiaAI@Yw8=IVAW87`Yigoqi-26`Cl96iwvt+4*f7rJ3*R%FbZ0I1j`! zAEou8e(N~dGUj&Y4S+W`lL6HBjPj+FuUcEmc!QDo3LZR-p@-TH1Y4JX8Kq-SK56F|y-mkQO0 zxA$wrSrC|wj^X2yPyt{i>kwoLN=GqYzptS5zNdLO@LQ9~N70&r?;u2?JdEO{D*FLh ztHEnu?Im8g=Ql$CEJ{rIr^x)a0q`XTsjE?LETh_hbbzuJwT6I2Gum?j^A7sqk8H4B z!M7qlKFyBdMdT!p%q>YQR=O^3>3&mq)%{cBTa<{Y37{u2pFQ9?Pu18#8(P13OYUvk zQ28T>+FDF@t0Rv5tdW?KtPNbJI&1D4i@^B)41J>O|UOeD6_pgx~>p}A;D?@)l$ zgd3>We8148Ni+|>y{&epGt<<*yTSBvr{7z(?4J2_^Uxn)>ONviAt|Z+hNo?MyQAGt zlHTG~u%pqN;;yS%U<)qO3z&DT(pt^DV;mb9l-dS)`ui1L1gLQLz!v18nJ?uP-^3d& zf-QIo5V<>=4b-|@01Ez}03!B^LcuKj5cqr?s)MQ17$iFYrzvv{G?OkQLOpg0;w6yn zwPJ31`B_I<>}K3R1rcWKG=!b`A*Y5JS%T^L8IRD+TFSNFAd>L-LosSIqs2x zE*8UgattNmf|+d{yQv%GFFWi%z{5Dk#D${qt_Pvzlh zmAMCCYOf-{uO2-zz^?M+Z#0$-uZ33R<{`Xi5^Im|NUU) zHt#+}UhSBRHC3-xUMX8|(Rc0U5wWiA2?k67jQZo3lP389A+zpJm>@chdaC$7$w1BY zX?S^6j#y8*Ot#Qt49XD3L>JBX2Vc0fC!ZI#{QPL1_`Z8lNUo6MjAWMh>6w74y;eQb z*`^e4N*j0JM=4q;u&6hbe|zbij5a^MCsLdJc{DG9c!#^b@CXxGyooA`EKBBRb z2+YQa`Xq|hX0nGKg5blpT|X}-W$6~G-^SXE$P3%_Iq&qJW_g76L~3_0-Im3=e9=uF zUr&B*9vY%m-+aC|3vOoYJueI>C7GVDe*z3GiNPpd*H;MyMbGq znW=TeRFE=yvy5eRa&%lECPj`LIe)>dRW!ie4?)S&h46<8-o)H+?$)Vd}`g#5Mr zz)Woz%72&5V!h%J?BUl-$ay{=1sIA!B5B|e=2Z~wIWhg?a<7$_jlkzf?_0FBr1tr( z@Z+;|DhMD~qCs6bT_HKb#r|UB`JE^5lv_3(SIg$P2>1RC^*K_%*d-P1bvH^gjQFG_8 zPK6!n-&u~C{1X%BXdA-w9vUwwJ0by$kfWETs!t{$u=bt0iBe=pL_y(lEVvE3LzRYyU9GvK$b%1{*h^=?BCowSVH$E;lzbT;z9Df& zDjTt5f?ep)?dDWFjogC>cBcvjVXIxE*e~F2#Q=yl4|SLCx*y1W2+-9askj67jPxLs zivp{)>mzY1IY;;0pJi3GRo(Y`)lLx~@FoeFwcLOo!eFy~8lKietmy!1VO0$9ZhD(+ zpw>&`&(O$YTvw2@zS`8pb62+u$y_2DQ?m%-muV{+p`@s|Byq*zgJo;ZGuhPTeL=t@ zE-tPa;@c2r-&KApog*!+?Dm96|1IRVkF8ser=}Rz=en;s=^0owQpZbQ*jigE@Yt*` z^LS%&#W+6m4Nzq$I+;F&@X-x5>npqEvwlBOF0PYT>V<&kA z7P+%YLd{d@m;qcB7d)Ah7M6mHR+eS(Smn~4T6aTeSN0!E zA52NdC5PPxA7$;6`N<5DgG|{O7f%jhiDmXB9QP8@Hp02w$}!h{GL!q7A~~ZgMGb6O ztTDOE=QSby&*1Z>EuVt!jnT97 z1DZ6&GiEJAj=LXTvbm7I_1%7I6^cdy9|dEn*BB+mk2hbF_-u=hV}plp4guC%j~0A1 z*6p8%f&7z7a0|w;_Kgqh+GDtge?HNqafIsjS2X8X^({~mwwMMFAid#hv(rCJh5+Xb zu6datV(N_{|9f#70KEY64RCXtnMw^W*FT?3#Uura62)cX1hrPf67>}VkMs6dexM~# zQl{nUY@XnDnS1noXCpo#A;*n?>64fx}V}O`nRUW)+*UoA+_t%II6ol32|G zA8#S&PostF;ml(10N32JjJ21aQ^CLhLF#FsX$5x^jcdJbjYe;!TsR;T6v{6@WjE$x zg8}rgp5*IL{#R_+4G^+`(aSYn$$p8p@ChuP2$v3QTLr%z)i*G^=YyPA^9f|-5ONl^ zVy~LCP|O0wmE0Thf_)a?er;-}pKMLe+B>he7~mq`DcxEBr}ti7IzzB9*!o2`h>E0s zPslUE1Tu(pcV1EGT1zsn@afDq#+7OxmNv&tU+%T^oz^OCCAkGPOb8WA9bgd^p55WHL!b)qYsLy}GK{qh85&%Rx(@yqG`o z=6G5|uJT4cg)VzU(Z4jXRZTghr|?hOA?G~+UB)VRnjNYfFK34Y@`wiRg+jZvZ6JAg z`hGh4j=0t?aGZ#gXJF)X2-hHocB4W)lN8G2@=G%-JBpgE_t{zD0 zw6Q1~?)N6Bg?u9o@cio%XLuXq_|q&~;?qBrL=l2%#)haQrkkGSXkuRG<9-~8odp{u z^np-q9~#e@{k{$$0dWI)Si%=xm;)%A06Q2?53tAbp|PJ{e1^3(eH)ZAnp(CkMKV{_Rvef%Gh@zYoB$pIF&P9N z#(nfziN9&Trw<4R?Iun39aXX+*>||WpxZLXtAYSql=4RKFh9=k&Qz;_L@e)&?BSV=ZNt|{2S7*9dX9(vyql<}`oAO7v;1cF zF2KvS7r^3zQg?Y~kSF|hjR^6e%xcqu8|>kK-2>OU*&rhn{Y9?Ggo=7i7yg?|=$a!H zZL8jnDqq<8cc#B2_NLcrHB_jzhq0traO-Is86`o*&fK&{)%wgqNiSbPOmf1Q?KM=)2&f|;+!tf zvj{Krzb67=AFiAFU6Wv{R!XIN?QrT1kII{sJ7JUtzxi}z@yOWYvv3TA{Bo8nHZMA? zUxW+(#eqLxHM_?TFA--;<7P$2gPf8d&5JYv0+B`P5lbO`T4@wt=zRMDI*zdIhv>fHd1t zts7AUm_;Ds7UTNvh9^$1L^Yh!k-rk2w=4uyBIBL8YalI$6#oDNNEz zrt?E@PqS1Nqm>3UfJJC%i_c#p z_MzT_7I%Y?3zZ8HITfT!AZovGJ!GV2cF)|Tor)C*3=XrqoOqC4f+hS<_hXEYL$VFc zIe4YS@{rE%2M8FdIjw3s#b*gkv1M!d62W@!tbsbHJ63u6l zfU;#xlg%<=d>7GMB`+9D6y;_qq9EC!S6P?WNf>tTpCym?#x{4uc@0a$Rr!?YJbFg%F&a(Ft*1D~6@(nJ#JaTdUbf~c}0a16%Q zMCXJ-D=>x`CX?4rb-o4UzRJGHW?Rs#g$MQV=g`^oj|ND!P4K{-ZFEkJi$_LrqLTV$ zZ3uEmCOhdjMRf3?`|?GbANQS@q$h`C2TL9gD5Y_PX@J1j)zt-+7p9fT)9%PIQ^fN;sJsI*Cz(8$N zunUcgZSd@NTZou<>P%*@eSQ~#$FPoQTI7h$Q1MW;WwBj^B(gy_3B)s@{L1X?Vsz~s z;Pry8U~m1{th3OeR93Y5Na=OJ0qqa!ico%bwxLlGxZLI-y1p%-_h}1aJ*& zfatL4%dYd?wFgjuI>7LZfqvMHw>d&~V`v1tXK6$|Q!SM+V2|kr`{rRr5@7o3`)RRq zW$SyI+G>+R>U4JXA?>-DacI{*d$>4zk_O5S|2Wtf#T5yp(S{RBL)luHwd4;2oSy4C z>DQuN*06o0kb?D{x+xsXF1G0!6@!lh+9xJvkpn>w@Nr$J83{(Uh*dOTTz?Hw5VG!e z&6)(DY5M>y0tR)xYN`|lgg;H3iMO$YcKphC5~BEFlPQ(XL7zqxdq_@iUOotu)>G9( zh?NBdaD=;{ApQX`srhZoKzt}2rrBaq_s5rzEF`%N0oEC$=~*|#8)L(9@zWk2fcW_Z z+(!f3hIC><@&w{MYEqmrJo4PG{T)NQ~rnYqrI-faaO@ck=drQ3R-IwQeGVaAg_3Y;?Q>yKE9O^GXDeAkZghM3AJP2?+P0`wasIxqx#CY{pX*Q?jE_XZ+XL+3)7+=JouiH;LTr z5^)~?d>quOnq!w=2)i~v9dfcij{~1Q%# ztqz)$?cy&w@4OPpa>Gdx0rCeG9}iNOl9)AWb8gE=pJfhUV{TSvrRvTGcLYQ3d~Pn=u1 z8eQIqrDiWGos2q=$#Ye)a@fa2BZY9r9}-7l)K>sRklSn=Z7iwXpJ65eBGYr(=pZFM zBlj2L&hFaW1WeDuj_2CooOMpLwqgGV3jmB(Le?uXqW@OG&`lj8o;X>V&>?c=;YFJj6YxUNEisP*3BT*h66_!eJ zH7L^4FNVaZ$0sg!bR6)<&Uf4TWZuObd9|pO#-WO1T=bnEUc-1u6S^rR)T=x!nf=x2 z2NTu>2-s(4vwIwHe;~TH!%PgYP-;D}+$n(jzD}|U{I*i{_z8tM4dN#tThQK7qqNM8~{gVGF6i`0jsvAJ#ql>mCG$a3z5gp9od`XeNivvs9eI&pHJC)3(`n@ zZ9OT6Mvwrev*~`H%**$*4Ad=1^2c@|uZ|l-1|N#^`MfY5k_1_()qz@$?sv^`@Ne3M z>NSNMR%)r~6HWFMg7RGt_*~54wqOB#oVW=d7Q|)CcHTvl?2iuq3*WMWE}yiWhpsmX zD1DK86~>wz#w3|DG_vqR;z|zP6+pJ@ZJzwT0rc;Hd{qEiAaEjp6@th|ywbt^W@@*xj9L0NMmXX7f8kmoaX7Y*n&NHim4zmU5H zj)s6UOyt>{ngG4xmJRs2X;(f+Ym7c|p8L^g;qB%~k?;`^hkv#KhMNw&vGgpJUI2p` z3WK!*XdZ%&zT`_50U@Ht zlVL`ncc}i!No0b`2WTByFN##Sa_1#dG2KFo6`A%V_48c= zK!Q$=o(^bo0Sc)l#y9Ev1-05%zGg!#45ngn^vLd!R#sLHDU+54sZ9nPHcAJp`DnUv z%w|hbklt*&?I7%vJq^1{wDzZ(xiwURl1C>HYu?_7Ln|}2`LL7R7d3z702pZhOOzZv zLh5eu84jM4FfE~i@p@!wXbqW`L|ou`nk*YlT3XmTea&fwi9!5+Ayg>01=C0TL_K}I zl*$MDq@sL*SGU)Z5=*L`6nMPM03h^Cn#2CMc%5 zS`Huz09&L3FFk?g-vfhHYV>L!42nCVBnOhR~4`p{aUgD>L&qFjxj9K4R@K#L7q6TRo^wSHn$Y9 zHJMwmqgwGMg<0rcg`4-RoBU`?A~|Q|IKY!%#-m)tld6<7CBRPnGe`sJ;D;VzjK5p_ zRwcrGA5A;hrDNB@E9ZG3LhbqJ*UmIhg7M=74_fbj(Qq07n5bW-YRawbz(xCvOFKu) zTxlF3mvCRZ2!!1)xZ4jsybG$M-mIoQv&HH<^wpx9RC{f&1+U#^_eZvgRg_P{%HPrH zYP96Xt!cH0%_bN-)%wMKN+OizUp}I5Oe1Li`YMX8b#pfhU*Avv6q%8e>p59gkjtk& zBJScbr#_^mmMnGa<|9_K%vV-R%I%#SacPeR@@)wio#vs&i5 z=dKIRo=Rdgi(KiNxhwZ)m~#mZP)%Pb^-@o$VpFM6IvYU;UbyJZp^_BkauM65%vb$+ zdvm48P8R$NHmyhw)ra9sZWH~PM>%z!=3V;6s??;-$;qyY;8WSgJy{x8)(5Tl7Ap7b z072P$aS?ZSPf#u0aHC`K)XHMuVl}8&?9HbzZsx6FlA=4`yPr12@=O zA#JnVzkvB&iSMm%aEQ*KC&mi(k%m}vTcDtV!K19mFlgUYEng0^}2!CbFryTP3lfa3=d5%*JX80&DxI=j~k#LVG-j7oIKON-=^Ex(NpW%bx4VBPOvCYs+@TR+vo%O{gJI{I5BFokfb_Mc)K}NZp z#1gkRah5~>fvvgi0A7H1;1l<=#3Wlww=(jo5c%H-A~UhSPJgY7)QvkA_9*1`8A#c`wKc=?|PxO`c-^XMbyQrG(luh6%x^j_|7p84mY zvKbWjv}fbl;1|~}-hA9>d}go?EOMrLy5f;?=lNPwHR%_9Bc736O=hLD&pf|+;=CUX zmOcR~8 zmSi{fb)pDk&29$S*P$$f!Tj#2=jr);fA81J>-nR4yx;eI&ULPHopYZ%WX$O0p^L^p zZ*~$!5ZpAF{?&%$CvJ3QYo+AyF;WH8EdKNYOs-;ch;RM(iM5Bil21^HhoLd-xnueq zD~_1(KWnIE1-W-faj?D8osbchWZDfT8bmujRnbi-wqgR-+u=N1c((BrI>VY8+nc5N zVs9FQJA2S_+d9-sh*|I^s;*Jz)u9UoZ!^bdu$bB-9#&&8!qL*hs}fa{9w{?vkrRcI z6;82!>|+)cXL5Uc>gd46+sTrQrj))I<#K?^)#0m!aeO?Q-%z`x{wFL|*lT$H@A|ST z7BNl32kW$A9>w$U2rIZ1$!m-jMG&b~Qfn91u<Ph7l|PirNNyg_k}1}aNOh4rTy8HLTU zDg&r=ic<`x)(kaFOKBb^JO*y4LW&xJHi7DzAa5HXqCNfd-2IGNA-_Q-&b@#o+(Kkj z2mx1%jcpxvY2~sqcf?#@DCSnV13(n4Q>vQ&mF|xA)Ndut$H}JMkRIi@uHxFPfV}po zK`&_Af`vy9@yB7r@-uEKw7rl0snqs9`&kt?d2G7h3xYiudvN6!vX%HF?K+tXuIGJ~ zPQ7gh21aH(6d0RT3j+Iym0%~QCZ{JG=5m5s)Zk0s1~J9Vc*~cnO^+`l>4kUynh+09 zd0_~A7w-DP+nhhVr|ut9*G{VU8KX?-+t~cTL}w)HG8h@eU7I0VT(6U<6JvfKOr@GB z-0E4t=&MZEd#QSNrG?oiV2(8&Q(atKU7SKI_ypYB+}c(;6soIO*C->od`^V68oa8W5{`jhX+g{<{(RGADt6{5%`Nj|HlhOme^}NxHj(tc+hsV{s$hi zoxq&GbD`50(VjayQA2JM#bPB5TO7~MKMb&w(ahc~oV@4vT4|r+X~R#Z4|y+V;qBa6 zBXIn8aIBW-hQQhb@UN=nkFpKT5cUlIV^BelCagf{;qkLV3udrC+Fh&~&6iG<6(k)A zcSAL@Kc=4WrMFaKs}XH9{-1X{?m`)p@1C+$sY#SL`O!A#j~!^~w7ojtUOchh@@DZF zpRv#5SF&?Q_qfZ3sw@Nnz|z_j3>Pi=>|H#0?vT$u_mv}nigRNn5&${)SBFLBvB3u= z`n7uvbYlTU^kZ%qy_&U0x@Z3Ed=PQZqC9aO$RbJET7e>KjqHF#GrH-PGbxrcSh|6g z8~?ps_9I?+Al#MvCL*Rnm?+?{)D7x#Tu;#D`8?sMp(~Ea+ABZN-=$rLFA=fzaAl~V-CxP0*RL9l9C4WX)hnTL z56RC?3|7RK6L^_{1ihuLP4!s45&txK>^J)}ztAthh%>p>VdA>j`QS3(t9EYQIZ02f zf0$^voP}6C5i&jN5?-H>Hx&J^8%IWdD@VCe+jb-8HI<8)SPK93hno){DzIn3F1F*8 z-%--B@fje%YF*jfyJqP<V^mzU^5=WhTg73^(k8mTmc zC0-xK9SemJ|M;JWHR4u*CE1l&DDVF5M8~`TKoCn(tMzHCj+<271Uh%r%4 zHTc=7^Gd|`x}Oj2PVqsig)M-X1A(r{TL*^-wWkN1D=4mV0d!*-w0_KVzL0P%ps%)l zEHiA699#TZ60MR;HqEiTo-lt1yK!&johI!Ul0YmC9AqaPG_-JEgyG#={f8 z46u%N(;)fP+zVnz{QV4Vk6w!|@6Fh?BhI=Ry0VeA_3$uB&sb*Ig#vSv2Y^_TQ;Ri1wPf&eVzvR?Uq_+AyuH6TfBK!r|HmxqP2Pdk#2F_%kHYtLR*`J z+(YRnpY`cG>*$GwLxRS@>40nICROSda5D;@gbqh+UZ^BD$s2wBpJQq{sOK4xU3p{9 z{xJ71+@3^)?LT?EtMq2VqfVwQ8H`isC}}3JQSUCNnd&jdB%8aYRVn~1X}~Q%3s%~t z_Gp@AJKoDn%eQF`{fBj3f`y&kyX^VUXNFQzGh6;Vbp>#{hWfCy|9>f{kTLKsReu~I z8(8*VRAu^DSn)EgdY`wX`Iot!j2tG|aFTnMF~y_l_ZGztXWCQ0Cagv^Y!8dROqY6% z=L!i2;X2#-d+tA`8`%?GwyObIjWrkKi4}&po=9c3j}!g=zqwd8EuPe!d_*M?c>RAL zAI>m^4=3XJgJx~4oi6z3?$wRvuf+L&#E-A2w9c1##^rTJ{|ZhT1e?WtcB-EVA70xM z=a|*WfQ65FG>zpZE1?j7aKiE(u*TS;58x)Oyzz2m7FPGkjFrX7&lIZp<_5b8e<+@4{Lwc zs=CndqQ?;oB<}jN1`R&AlJGpTgai_HqP^Xp^?YwJJzkADviZN*5!H0H5nxB|m=@cA zZ}3F|0ri?zq4pdpg7Fz6uCjc`f33<~{0CWBe*hj41}-KU9m?=OGCRMd0*#{Zv1A2L8DXiRxM9~Pw;)BKIr zhdn_X4PCb^){%grCYW|VW>{@?-DuRt8%+>tPPS>K>(uKH(W~t~B@M{L{MmrH{Pv}g zZ)z1zV^V*9`+%pyWmQjiZoMKF;ZP_PNhvAYUqPhD|2!S)OBJ18ksq98V_7Lh|Dg}w z*ycr*!1Xe2h$HG|c6yRmhf%`MY2-6!koT26^2F580^KhF*YG7c5IoQ-sYFMNeifk}4CW2!n*gvtU-MjEQxa3~%V3TWku<2tC;ezQ5$h8JPy}B3fBw(pWlj0p4|kOd|LHz^l;#WpT>M20$##4E}y~A$KzY zo>nyu4{O-DWhYZT4i%lmZ(oxgb!sIV_@LivVYN5`_Typj=9ucf{~7~2U_x)LX6x2q ziH+)?U#^ZhfugqzicBhs2Z$P*OlM!%*ojfDy!D@lYlz%~u991TM0OCvo;Rld?q3Is zQCv=wn}X&Q`A@fRgaOW)dE;E0C?O!te|>@<7@9_CiGAMd)KS8H>xYXnouM3ELvq$B ze!9d370u3%=c&v{T0iN=UUL{Uoy0QnuW^C@=Ibm~=!8=nu4a+^WdeRZQyVoYCt~&^itA9S5&xTFH8Z~y*I|ni&-9jj0q5Gld^tH9` zK2yYnB|cip0){p!G~hBD2+=sEg0UP|b}IU_s6fub3=mR&Klwu*6vwpEbf!TcldOJV zr}EHok%N}0;otnQP4dKD<=a5H8BO1_g&_^byG!4$EKC$5uS-@7>wN$cO1)Ji;gCD- z`X5&2XW5MOL%`LXWerZ~l>c2D_o*=ac_sJCYgw6{?p0Ioa4JW)Gyeexngy&Oyq+v! zgj`&w>^!4(c#=Eq+WDGbvd!Di*TJHtCz%OOGutu@&>6)}+;A^7LhY#5);}@=atfHp z*-P1U3%mw+V(L0kb<82p7-yVe87WZJCs&#OfthUMpsh+dAV0+%O1T=_$wbQ^Xn8a) z`g6#-CH`FmhzH57>xQD5E-&Q2i#+heJ^3xnJam9koup7)ya&rzxW}C@g~+{%E-+#{ zI%1PV42AoJYa?O6M7(q#Izsu83zBl2SgbB_do(m+??KnYdBZE={#S$Ed!eM-Yw(-d zLCi5CJA%PlvyZ_73H@~n^VOba%;#`KuYE`C{m?~YAVtQH*mUPRroGbw5?p>d7=LLf z`{`}@fK=0&W6fc(ERm64z>R0!YvR+X;qyC@>J~S0C*mXTK1(WMSmUkliSc?iW>SQI_ZyU!qlNRvxOPWE<#*Mx9Kd8xnTtP z?oiq%fAMYgl6gfW|u2;;H( z&|waFa(SwyH+h)Jld17}AyE;d68gVCsj9F|meyGNjufID-YUAUkAaT>Cxeyfn$V*^ zaZCuAit+FmgnP#=O=SjdY_NuGU=d6od3o-=rZT504!O{Moi^(4mlWL0kWFhnbtNKt zl5n{!8B+(d2Pr=B!USSe&R>Q0B${26H`7`EXmCB@og~ocI=i|M9(RBR@X3I?{p2ct zt%c9)<{9tmRf{r4wW5Y84k*MkNYU|to&oE^gqws{ZZ)jx{c2E+VeK&aSW^{2H=7=? zjx3etVAPIFO4FFn?9AaHH6g>#<$ZYc-nNnmBwmn?2oW`QgVPn1~mu}o~2+|OR(tkkK!Df>284v>{HDbYz~+#BGi8F)GKP>loz!nk^`30kX714 ze$Ragqtr#6IEAHf36>k7U+Cu3xlq62|thJNWuC8s(%e!1@zwL zc|FmOXKocxP!@o6=X4GB+1+}fODT-1;hArq5UWA1ByP!m+`aWpPu!&oKa~9lPEPKYKR|xGgE(+LQZXflF#t?NSgKxw? zu9374V^6hI4DlxVZBJUtAHS_MmdP{bigz*ODYt|U!#5XyT$x_ju4C+wfViIY?W%dD z7rrQkRhyH;hTg;mV1?d8N=5ppgv@%N?<_d8fbFvuzBQMSOY%!W&QIly>B9TBY&2X_ z=)>F0!Y3wNH>OZ@%_v7U-f3lR0M{!QRCiF0Jn9W1H@AO27Us(#2T zu9I8`mt2b*0rqQZ(0&GF!05!ScLs`|-uCA>yyTWsDT`_*ZkK_W+-a4{UlO(X@gEGZ z+8UqyPZ29o%2*XK!0vvb;QU`Q6yvk?q|nt`Ip`yZU23=o)RlCKO1wmj`SEqKgs-L> zPJypjGR~WYehf6T0kQ;qA0E&f_EEVcgvUV1J>HL8_+S|0=zxh}sbC1#y!x2JWf^$D zLtChOcr%4Kfh$1ft3U03MA5g2YyZKi@r|oQ<;Ju-&3~UaT6K#;w5Y*=_$|M38X8h* zW(DtZq+U92)cLy{rb!Hj`H$)e_n|lvN2*IUij}{XeCwHFI_Gjvp9isl)YBfshPkwQ ziOYx$xSy2Uwa+^F^fNaf6tD_{yX+uHh#Br;Y`MHziX!tlU))9Hn?n3)M(x|~r;LY_ zaDw?TAK^fnV-eth20P|e)BzeiQ=JF~VF)_$KC>BgMALFxKw?VDx zfP|vsi&=!SJ}H+cPMe7RF}mn513#%S81ElPRzn%ABXVnKIHsaGFEXYrE-dv!0h`9tT12B%>1Wv8d0H)G3_=kviP|N(J$R9uKz8A zX|jo#e*rEmeBMZB?Bl?c`df#b55k;Ao#9<+kgML5uDBF(grO$){_pPVZRjOI`S#VOg?L72e@hN-S>F{ME2(+ zY~cD7)R)KSR6VYMlP(=~zwKW5DtKXF_7s;&7%U?J z^v^v>k|782(T2=24lF-qD<+TpJG`X8+#Y((aCApNc#x$dK`;C%+?BP7Nti1$&Ph#; z43xgA9ERz$m*`+Nsea4`gTvIxRK-I--)MEB++15!^BR`09}L~{-v7lMO4lz5yi4B; zWuN}4aMnhU^94(E5&1|wdFy~1@D>AB zK`k3M-#ZNX`SKZl+X2klHpEPbh>0iurU8`Bpq4FP2^{I|~l>v`k%!mkrtdZ{yl#Vk#JPy+fx9uS>55bt*>jLf zz};NbVhb%eoi^7%Un)>4RjPv>rbr~c*^9{~ZG~5(kLqu7+)7L;LTzji2JlT%eJqLE zzRd}fgx)ADp*l%AwQxG_Cnn_1r!+0=q_IyL%rH4}S-wD2HHG>7F-4;8cCtMC9?c%Z zrXE%{E0K%2_JyIZK;Y{VnnKb-stW3H((dp!jm6R)(oj+;v+~A`)5;sFFkJsWRvFCR zj#^TAdOPy{Ye|s4QXv;KdI>#OU9%QLMY9%>YiM_#J0C+SHo+Ywy;+MD0D-bHKEKvb zjh5BeIMSUWIFcj0tIISlmgt9i`Unc$P+@(BCyvpUHgOH z4q7ff5zxVefCJT$-)v}l>4uk`V+3WK{!oq(%KIYMz z6!U%9^!9I(&6NUbx_;*0e7y4IzYA%&h(RX81A5b5gtOBy7^_Qn^Z&HXNs^(e5>&ib zcmB?1zV%Bc1o|jh#N`3yIhwW3=j8J4qK8}kwH&kI)#2dE?%6XTl-kqBz5X~IL0t0K zyTjw-3GbDPd7SEp99-Cy{-fq8jOz1#6SZ%YE+Z!_tv3a$z*WSy7Fq2&L0Ru_8`%76 zT*a2~-xX!9y*wn2gd?r5K681yT#1@;Od2cD7>u;%{kIpOB%$Q4>`otQn50U8RycG9 zt7>GQQ8uss)^%aGQMliA`}(2WXvHiIdDq6!sC&l8Rw7T)ykC4_yYp=aeG5@H3|#3NSy zdFz9wX2$;2uLFu1q!J_y^Z1_+pkh(hvlGl4GRI$^w+lh5&U&6ImALb;^#Ss{-Imb}jijo!L78I2#c-Tcq#_HbUo(+Li{& zg|61#jPyp2dV%&}zE!r>m$9)-ivBC6&YtB#(OA%sBl3qmJn!&B(ts3Z%@u00JSP|f z?r~K_V&Pp=-Z)0*U0#Ee-anG-X;cN5>8AZ*Hg-@|=ClfKySSBY6IO_;o^RQsP>mmN zRsuQq#7v!`=ObGpLZZkNWQL-I?gvyPHIiB;YapfA(`()=9h;n#|N4__)1q_$Wh_Xm zxlK8ouN|#$9{wTx18Pky3vY9iv`fsc@QANc+(igS*s+_IxTJc)kXciSbR1PhdP{(o zT3|oNNp$)R+WmDBh7P@kWd^m%=~G$SiMri#2s+Hs0!7fCp1Az*izQQMSC`%K+AFT) zBV;B?V;JN`aYqawXTJ?sW~0^P9LXa#Rh`%2UdJ zMLi{HUlf%WH&7G#tkWWxeC^QwOW!zU(3-HOJ)}jV{C#_hR^74s9!x0=hNA@B@8nPuN}_N&(yy6wRxZVT+)SkmDdq`4IH*)kC~mzWkGTg6lJe#fF8ulVv`*;yh}n z?^jPz3M%<6-)wyzw>& zOoH{}*j@Fkc3|3bZ?n(n zwH%WFU|0DzQRe7T{v){ytw>(Lq}Ogm1ooqymuTI`PE$?3x zzrXV2bFWYElzoz%MD4~EeU)_G<>yI5)KqadKQh^-CLO(U`sM#k@+Q+Rg-S1mz@fi!)6A*VyRM#)=pr~| z3l$~5rmcE+AF>(JiPw6Wno4TdKU=V!l5}8_W4Sum}Esv{|x&OvHkiatDrkJ1g_WMDlVOSojx$-teW9Zl7HqDdu#kDYb*W}NDqoHC|XN&)TGUueaKbwg{*3@d~%Pmw2&D% z6VQ*^1w1oW+ye+-DRIRqj;mzce5M;LSVL?S-Co~>2u6)^Szb>xL8#|%4snz8RaYewzXnXfO-%K2O7eC`;y z<3Lp5K-_%`a!DYc9OHUlrzwKd8s8T8i75R|s+c!;CJtmUe0*}B*WV)N5#i4a+-_tG zC64WCT>N@FuNE)CdTfha2Fckc*8R@DMF<>RB&XqMvgj#yTX2(OaXPoe32JL(chATEj+_L>(hG)Ce>X95OM z!!TTS7-^YYpTLjgeObXDfq}>5&|T_jF585{po@XMjV&m}T09NSpzcB91#S*9hNLmj z5h%9`%^rT?Bl%9AS#;#$p|UKN57ieHuUdmgC02&DqhxswxaMu&JLtI>wQ?YpYUu{k z(ve>JJgBv+(@a^nJ;tjtSLha`$vhwkZuHTPw7&F`zJh*dh+T=wv{T)f%x(#f9WXDW)|$vGkKswxP(=ZGO}=N^gAnk z|1yxAbpba%EKxu>8Y+Ie#QyHm(Ry$Bot&4PO0NN5cEs4F-7zpw2dr3x{+9nnf7-BU z&d(q!;{Uay8a>z<1UDpqCr3g>(w;XFua&mx*rIJjs%7?IlyZJF8+fN|5ljYY#;n#Ly4-zSZQ#`wa8*Nz7t5XKh zD(+4BeTI`o-+n@6F&OH*)mPJPX5)rQ_f)ewpghjBX>HU$#gCa5&q(f_=>W8mQ^2Y^ z_DGt_gZy=Y$E%1lo;-bDQK!C1NjpL(NWyq02@@4!NBh}Kg?yRqWl4Ti>4Isu=M6X+ zpWL3(^Wx?)Th`xO9Bj@R(<=L0{QU*pEB$eggT0KY(2P%WAEp5vu0DOVlJ2OH0XjUymREqY!#O|0%-FKP4 z1Jnn5pd4mbDg7&JM6yId;5+0aJ=DkMV_}{+ZwUVRCqehS724Bv^GWoD$yd|V4Kkc) z+x@Y}JG8?B%JW612g#}Cv4n4hDQ4GG<9^afnpQ+E?N3}{z>9prM<>Z#ppD^<^OKMq z5;CPJ)Vr7AINpou@xeW$8NReT($-v$Ka6ng5m49Y|89Tp^_QIO}G{`E2H3nmqZYP_3(qHikmMQh7527g(2jY*IJyZ<-fjl9amsV2u z*2S=C*59TrEaU_2g?7X?gl-sbbI3X01WjrrXATD;se>C73%&YSTtHsB9|y7A&Ckyd zcz^ynQN8*@A-Y0=v@b3;3;Mvj>%J=HSmFKSZ>6q{H*yvf8&C8s6FWR(Rz5Bab~1y; z^z2;qMu3Q?=DC}Ds#pESGVb%0#v70@DT2xLN5r}Q#YdaP_csZ_6S;o2< zndJTh+mz~;LApAZpBL;xdIbtiS-?lE%%4yms~N=5VQFDlEOeK$G^DehV;yT~FnPlb zI(+U7*;=Zf#L(r2rxT03H%*wk!>y5VG{Y~!#J;|lB;wkcSVv@&@SxT3O&HAsfQc_h6xEq)BCVj@0}c#eA4tQlfRUC6FU4#YyCb{k46=Gd>OjmhlHZ- zv%BpJkA&LBrxM5B@u|uvf`j0Br}YLk2!o2)pDOgF_K2U%0pQ5Mm94q&!IFnB3MYhhR^3Zg zQrxO&qE!z~?PQ28`b9W&591B)BvZYZ@%!LbYq02-fIj$zhS}`6_HJx86+K5^zV0HZ zUSNWJd!Go^4-^H-CCe}zS%a#&hQ>WK{VhZRXyMWAXw%MX8 zBPiLuN@9+#F)G`&BbFZokK%#p`^z7tpoCw#L~br-QJEDK?ZNseu7A6O7}`s$8w)Ln z?XodEi&*CFHI#lN06Y z429@L+5#gb(`Bp!v`W&GYSS^x^IGzvVLVw0i3B-!9X_>_4a8p!(swGzQ!=AdLr~e8 zPj6n;M@u5~B3oD*qsX|NsYE*WHjW6k3b-)6EPUAIeu_^kbpt4>+Mr=(ZCSo6FYkJO zZplH1B!Z5CRPr^NLiJyQ-`Ve$iY#tu;HvAINUbR+$(np|9Y$g(y><)sQI=7L(Z@m> z)>d@kB`&3)tkZ?^puRx=MMHJk zeY!;B;<0*z&Wv4F_+%augX9!VG_Z8X9%rEO^J5y(-Le|+JwDd))v2j`vya|fn^c=* zcb5FJ>Lotb2;ugo-a47j!I8rgq<@aqllF;bI-%dGwJjRQaG+6SP`I_jLP#H>J zrDp+|cA74ihS@>IB2}hX0ztxaSI2LJj~4gLbTa6OFRL_UoRok=E7!)2YS3QOCpN@^ za_eVgYR9lC)4+Z zXWL#n{G9m;WI0E0(5N)<*^nq;9;=hD;!VsH{;A=3)Ue6F4loooY!<2467!MzA?WFB zo-^nS9R@4m@R1sQ^da2OhMbNj<1UCd()lhvID@Pg?AyI*gHs$%+AJKL)^X!qd(@tH zrh7?Xgj0K`YQCe6Pgq{tdBm0$ZXYRNYE)Swl||C(OadKFc6#LC`o8YFt4Lt<(s9`7 z@=;7PbrUsK3UNLP8|GJBH40zaTOM|COUW*>D-`=;`{MyRgs-2kFUL^QSU}M$ULvCxy^#h@ZduY zLNtC0PZ;ruB>UMK9!5&=#{KkqQIEIFiaRQyES5ohfK%JQbz+BQkSdE&aCKrXBic0X zwK(+FHl}aB@OC3ROuZYV5vS^y%bH#2o7W%o-D}h0goi^Ga`{&}A&j*61X}ZR!*SAB zC9({31fKDf*{rMYwdtQh&l0|)*RMk+o(P?&2$8{sF%G4gFJlQmnoEAthg(EiKX#e9 zH+=<0aHhojXE~?KC5{zolqh+T-6=4tzZSzvr>r*rq?2s0tIXkLrB`%}B2_WxIRv&m zdB}DmSpFrrj0R~Rp?|*H+p?Rrekon^MA-f*E%D;V3O9~X8YcFD}^U~W*@@OS-V zAe+Xj&aazNupU$$#&Rr7IAmPj{wX6Hw0})CAA7Sj=L*VyG;fv6jw6ushT(#SAfY&8 zd2^4Z%@9tYGh4#ab+5T*k?2JB`Kfse?(|YXlg*e6Z#*SmtivnGEG=6>CP-^&Oppmkh6b?$rE}F`k@+t|)V^ zwTMR*2HAFeW$8QWeUdCu+?FdrzBoswBQt~bfvXr;FHbptdwWT4@`mBJNW)fH8Ki& zY%R0fc)A9Ssyof)@qiho$|HGrx)iE-=Yss}zN_YbuVX@OzrvfJ6>OceYSXgCakw$` zhooq1Z>3z5b`NrR?~XN&vl%FHhPtDb<7X)#N{KF1W1^-SzJ$DmW4Ei@5Ys1f;#_5? zBbB@5Z}WWCfw*dHpV8`)w|xNPPMcBD_$4mqbr=nFRbSi#5QLom1RUB}yrR zgJ0FQ=4*5!&$VJKz^#c4Q|ya8A7iT^gpEt$jR)R3Iy1+UT6`9CY?o|7^%sO4hqv+X zKuP%J{+3BVrn&a*lqz?TH2JZj8!%%lccU!2OD~9dCQF4Ye3nKj|6@s#)e1JwkVv-W zQ#a$hHlh@ssAn5XB6h+d;9SGI`rgR0W1p7RYn<&LIZaZ9AII0GEj3Aea^|gdHqJ{L zJChaea84<4Oa$A6-qhuH2_$Tg^=R3dTLVaUhJJ|Rjztfn#%u9!lXvfsX9{vgqn=c| zr1Z59e0#I(vHvE8WuK%gw7srwL(ut7+w|ogw1CtrqxF;}rDgn}Yk+uZaDGzAsVlk- zK8?EJs^qYd>}Tk^Gu;KX`;|z3nsek=t_GBrUzk+Q9uDLbqI?G{i=0J=7@+Dh+qDS( zxQCLigtVo~_aal^AV;BDf04$NVbuxKdXha67^fOb=Q+xo-Z*|E_xpgP6I;`c-~y6C zYZ|odw%?wRJjid$)y-pRrf;|=ZPm~j1$twsMxQP@dY<^fF866ELpZeOM98^l7$}6C zOB`#{_^x^_x=8&};|Wx?#`ietd2w?dgNZLm#{1QSbwb|1xu#b9LNGztr>W!@k0dym zISlUAHm&&Bwy2;*1|0p%c^u{|=>B}?C(2`#tY8lX#htF}@lswb8l7Ft&8CREnW0Aq zzAFO?wED;5@@MtlyG`=U!4!%47S9vcr2P1cf+{jpw*-%eerc|J>WjP0X3!eHdKHry#;2S$jR6c?jL4gAGkdI)VoX!V^f8qqyH{MIIAbqEHP9GP;bAKCHZ)Y+^KCst@sFkiaA(kZ zVZ^TRFI99%deRPQ`a)PV!*!A)%+Zm&Ot)LSj*Aihu4{IE^F1h#othIGuUaU0a(jmA zU4vp(dQiIpf$M8!YhgUJdZE|1x%MQcU#wWZ@5F=wc4RGsh>vWE^b#+Q8~oD#{1mBi zbXlm#ouxw+SjPFfJJ>ApYw%2{ze=g7CsnD*%MfzDow`FM=Y?- zgwx08uW8<5@owbB!NMORE2ISqUa&AhTXitzBx+zC#HPs@8`GkRo$b zqH9B$ZGB~S5j;aQY&}%|emH4zo;=ceQr9n|P~Bh5g@8YTgPxz-sxo^n)Dbf8X+~mo z1wQN8(zWDqLAfsJsHNKcV)=3;hbV#{OUj;QSX%@2`N4tm849@@bwOq>Y1t(fFDs{V zhBb#~u2ikwFO78?zo$JtuD*BnJoOrv{n4dqz+!Ib9Fa_8adXxQ2W_Kn2nzr6xP)y7 z6p2S~ z<7+McKV_X5a7A;|?EJ?S;uX!yji)bA8er)gA4@NrUe%15%+_uv3%-;6;+y^nt(aWj z(~Qe{Z(PXl1XAbBO|qD)Ye{5>hK8kTd(+5uBw>=$FpV3Fvt`kA9Z3oqWA#cFQX5;K zX-RSQK@BPFiYNQyXD7w$Se$vGsKgiw!Ib9HsplXr6?LP=Z;~XP;?;_%1UWf4m#XcW zvvhx-=M#QgAND58)j?wVS;T84o^szZj0kCHoCPb7Q4h+PBs9$~juCr&i^i}~r)Ceb zYd5AB9;BP-n2#zXH5HzRBElamp8NC#vzR<~7U?c5%lQU+3c`7_=Cb0e-f%0QQ${n` zE!3LtfS=WJhKVwxi@0fGnRF)H<%F@?(i#ir(=^)P0`*Lt6INw*^M0fuZ?l<4F1%Oj z}CBWej$T%j!y6G4`g7w(nXF_}LW;$}p?QAh5_WVc8@Kw+*bbqR6{h zX@3;6 zeDW6UXG`3qi_C<&;p8duc77{8&GFQ1`!xohXZjb9>@3`$|Em%k82MvDOp1?M>3ugQ z8U$jn`|s1%pWY6SBpfcYLqtyabFY|c^kK5iG zpS2-bi-(OAKS3XPKwJfyK3ILSb#AlU=*o`}SKkqkH8o;we*m2zv(YZ=2xUEKO!|^C zwPxBedDF`*_VqOzeqSL=NbT1qYg?${iOgsL+xx$^3Sv^i3~oF2fK4mVKAk7_yfOOI z#o$1mpCzn5aAnbbGz_@knuz+yg6TE5_oMi-=H`B64_2k*W$imChQ%JS_Ey+{rmVMY zSxCMiJ35NaxQEx5t$SSHi=!tLdj6B!57u{tCQqIpOl#k5|Lp}(nrrwHI&4M)YC_k- zIkj=2LE6mWA27-VZxC8V;|Y7Xl977+_LUd;e#AgKH#aWVwzag>p_y=}hP^#usL|TH zFY~eW@z!9m)mlp}56?hyMp~$pW4aGpN2d!_VZT> z9~rNWxU_dX{qgGLIqBQIKEe^rc+!TH{g!NE%w=7~+IO!RPrr%$Axi#?)MUXTUaR{+ z@~73$!6Jk4OOOd#`72)q+UxLZQn|rVc8}wo>)c2!r?JsRJzhTzA2Y8#^2t0E)GlMy}-g%`J;!wRGk$6u%?3UZiT8SPrxN?6YV z=Zc5N8Mt}7<*+fCwpeA;8Sjew5j!6WxfKpo6`qKwS!Lujef5VHzr$$_@%ekj_Rkt~@T4I+$zS~_Yl79mws5Bu;SpG!BgU6RbGr(s_j7f7yWCX;ZHf)tUQ3GL=WOZ=@!ov%dF81% z(ZLKZzz8Kt7=|@A$zY*U)y-$Y1x)!0&zQqL|769gsP@`vd>Z|Uq!Mlmdwg2)Q?}j- zcuzS$JKiJKF3uhka*NF;Z(HqoS%2pDY-UV?!5W_0!Cz3!NW49xOqz74uFgp%Mj+VO zx0H6K?z4pN^LqW>qF4t!e`XwdSog4&e1yrRYg@q?TEIj$_l#^Efre2%4_M27=wZ@o zUw=Zz>sZ%%%TCF=_kGP(8Eg|0HAz~~f!E?I;JGEj5)LhJ|GpNe)+4BS0@ zord&%W%%{?_H*J3DPyMZg==5&MO?O@_nV6UHI@bWQ9pJ>idqYqo}b$l*%Y`ewVoA# zRiTsbLG@e=hGyt4QCLOMxytf}{>&Mjdgz)?b;=)?OZ2e^w{#_U2hg8B^41;bfmUJq z@gPD7OCjp>yorrkg1FUl@oS(p*R7)CIy#hKRaow__9(}I0os!~#v`w=;`6x!03XEh z7URf;N^8=Ou756gWDug^N+aouZdFx(Y7rhHa#EMU)T zZZiq#r|^!~pwc|T`4gt?K1(4V#k;!euR}g#2yy)l&N;R3<+?}x!ntDXZTxY025U;X z&aa&;t@I~%%ghxB>*~Rd-8QvB$+yF}RJ0}i?app}QE6Kw zm57(@Eze#~Hj6IFG?=1gkXYp3KMc5huPjYWSmO2w*O_-{yZYzVZ%0Dp1RjSJCZn6a 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%0De9WP~;9$_cZA&udTevo$sg_p}O9 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 I+w1&)0e@gvQ2+n{ diff --git a/cortix/docs/Makefile b/docs/Makefile similarity index 100% rename from cortix/docs/Makefile rename to docs/Makefile diff --git a/cortix/docs/build_docs.sh b/docs/build_docs.sh similarity index 100% rename from cortix/docs/build_docs.sh rename to docs/build_docs.sh diff --git a/cortix/docs/conf.py b/docs/conf.py similarity index 100% rename from cortix/docs/conf.py rename to docs/conf.py diff --git a/cortix/docs/contents.rst b/docs/contents.rst similarity index 100% rename from cortix/docs/contents.rst rename to docs/contents.rst diff --git a/cortix/docs/examples_rst/city_justice_rst/adjudication.rst b/docs/examples_rst/city_justice_rst/adjudication.rst similarity index 100% rename from cortix/docs/examples_rst/city_justice_rst/adjudication.rst rename to docs/examples_rst/city_justice_rst/adjudication.rst diff --git a/cortix/docs/examples_rst/city_justice_rst/arrested.rst b/docs/examples_rst/city_justice_rst/arrested.rst similarity index 100% rename from cortix/docs/examples_rst/city_justice_rst/arrested.rst rename to docs/examples_rst/city_justice_rst/arrested.rst diff --git a/cortix/docs/examples_rst/city_justice_rst/community.rst b/docs/examples_rst/city_justice_rst/community.rst similarity index 100% rename from cortix/docs/examples_rst/city_justice_rst/community.rst rename to docs/examples_rst/city_justice_rst/community.rst diff --git a/cortix/docs/examples_rst/city_justice_rst/jail.rst b/docs/examples_rst/city_justice_rst/jail.rst similarity index 100% rename from cortix/docs/examples_rst/city_justice_rst/jail.rst rename to docs/examples_rst/city_justice_rst/jail.rst diff --git a/cortix/docs/examples_rst/city_justice_rst/modules.rst b/docs/examples_rst/city_justice_rst/modules.rst similarity index 100% rename from cortix/docs/examples_rst/city_justice_rst/modules.rst rename to docs/examples_rst/city_justice_rst/modules.rst diff --git a/cortix/docs/examples_rst/city_justice_rst/parole.rst b/docs/examples_rst/city_justice_rst/parole.rst similarity index 100% rename from cortix/docs/examples_rst/city_justice_rst/parole.rst rename to docs/examples_rst/city_justice_rst/parole.rst diff --git a/cortix/docs/examples_rst/city_justice_rst/prison.rst b/docs/examples_rst/city_justice_rst/prison.rst similarity index 100% rename from cortix/docs/examples_rst/city_justice_rst/prison.rst rename to docs/examples_rst/city_justice_rst/prison.rst diff --git a/cortix/docs/examples_rst/city_justice_rst/probation.rst b/docs/examples_rst/city_justice_rst/probation.rst similarity index 100% rename from cortix/docs/examples_rst/city_justice_rst/probation.rst rename to docs/examples_rst/city_justice_rst/probation.rst diff --git a/cortix/docs/examples_rst/city_justice_rst/run_city_justice.rst b/docs/examples_rst/city_justice_rst/run_city_justice.rst similarity index 100% rename from cortix/docs/examples_rst/city_justice_rst/run_city_justice.rst rename to docs/examples_rst/city_justice_rst/run_city_justice.rst diff --git a/cortix/docs/examples_rst/droplet_swirl_rst/droplet.rst b/docs/examples_rst/droplet_swirl_rst/droplet.rst similarity index 100% rename from cortix/docs/examples_rst/droplet_swirl_rst/droplet.rst rename to docs/examples_rst/droplet_swirl_rst/droplet.rst diff --git a/cortix/docs/examples_rst/droplet_swirl_rst/modules.rst b/docs/examples_rst/droplet_swirl_rst/modules.rst similarity index 100% rename from cortix/docs/examples_rst/droplet_swirl_rst/modules.rst rename to docs/examples_rst/droplet_swirl_rst/modules.rst diff --git a/cortix/docs/examples_rst/droplet_swirl_rst/run_droplet_swirl.rst b/docs/examples_rst/droplet_swirl_rst/run_droplet_swirl.rst similarity index 100% rename from cortix/docs/examples_rst/droplet_swirl_rst/run_droplet_swirl.rst rename to docs/examples_rst/droplet_swirl_rst/run_droplet_swirl.rst diff --git a/cortix/docs/examples_rst/droplet_swirl_rst/vortex.rst b/docs/examples_rst/droplet_swirl_rst/vortex.rst similarity index 100% rename from cortix/docs/examples_rst/droplet_swirl_rst/vortex.rst rename to docs/examples_rst/droplet_swirl_rst/vortex.rst diff --git a/cortix/docs/examples_rst/examples.rst b/docs/examples_rst/examples.rst similarity index 100% rename from cortix/docs/examples_rst/examples.rst rename to docs/examples_rst/examples.rst diff --git a/cortix/docs/examples_rst/modules.rst b/docs/examples_rst/modules.rst similarity index 100% rename from cortix/docs/examples_rst/modules.rst rename to docs/examples_rst/modules.rst diff --git a/cortix/docs/examples_rst/nbody_rst/body.rst b/docs/examples_rst/nbody_rst/body.rst similarity index 100% rename from cortix/docs/examples_rst/nbody_rst/body.rst rename to docs/examples_rst/nbody_rst/body.rst diff --git a/cortix/docs/examples_rst/nbody_rst/modules.rst b/docs/examples_rst/nbody_rst/modules.rst similarity index 100% rename from cortix/docs/examples_rst/nbody_rst/modules.rst rename to docs/examples_rst/nbody_rst/modules.rst diff --git a/cortix/docs/examples_rst/nbody_rst/run_planets.rst b/docs/examples_rst/nbody_rst/run_planets.rst similarity index 100% rename from cortix/docs/examples_rst/nbody_rst/run_planets.rst rename to docs/examples_rst/nbody_rst/run_planets.rst diff --git a/cortix/docs/index.rst b/docs/index.rst similarity index 100% rename from cortix/docs/index.rst rename to docs/index.rst diff --git a/cortix/docs/install.rst b/docs/install.rst similarity index 100% rename from cortix/docs/install.rst rename to docs/install.rst diff --git a/cortix/docs/license.rst b/docs/license.rst similarity index 100% rename from cortix/docs/license.rst rename to docs/license.rst diff --git a/cortix/docs/logo-old.jpg b/docs/logo-old.jpg similarity index 100% rename from cortix/docs/logo-old.jpg rename to docs/logo-old.jpg diff --git a/cortix/docs/logo.jpg b/docs/logo.jpg similarity index 100% rename from cortix/docs/logo.jpg rename to docs/logo.jpg diff --git a/cortix/docs/src_rst/cortix_main.rst b/docs/src_rst/cortix_main.rst similarity index 100% rename from cortix/docs/src_rst/cortix_main.rst rename to docs/src_rst/cortix_main.rst diff --git a/cortix/docs/src_rst/module.rst b/docs/src_rst/module.rst similarity index 100% rename from cortix/docs/src_rst/module.rst rename to docs/src_rst/module.rst diff --git a/cortix/docs/src_rst/modules.rst b/docs/src_rst/modules.rst similarity index 100% rename from cortix/docs/src_rst/modules.rst rename to docs/src_rst/modules.rst diff --git a/cortix/docs/src_rst/network.rst b/docs/src_rst/network.rst similarity index 100% rename from cortix/docs/src_rst/network.rst rename to docs/src_rst/network.rst diff --git a/cortix/docs/src_rst/node.rst b/docs/src_rst/node.rst similarity index 100% rename from cortix/docs/src_rst/node.rst rename to docs/src_rst/node.rst diff --git a/cortix/docs/src_rst/port.rst b/docs/src_rst/port.rst similarity index 100% rename from cortix/docs/src_rst/port.rst rename to docs/src_rst/port.rst diff --git a/cortix/docs/support_rst/modules.rst b/docs/support_rst/modules.rst similarity index 100% rename from cortix/docs/support_rst/modules.rst rename to docs/support_rst/modules.rst diff --git a/cortix/docs/support_rst/nuclear_rst/actor.rst b/docs/support_rst/nuclear_rst/actor.rst similarity index 100% rename from cortix/docs/support_rst/nuclear_rst/actor.rst rename to docs/support_rst/nuclear_rst/actor.rst diff --git a/cortix/docs/support_rst/nuclear_rst/fuel_bucket.rst b/docs/support_rst/nuclear_rst/fuel_bucket.rst similarity index 100% rename from cortix/docs/support_rst/nuclear_rst/fuel_bucket.rst rename to docs/support_rst/nuclear_rst/fuel_bucket.rst diff --git a/cortix/docs/support_rst/nuclear_rst/fuel_bundle.rst b/docs/support_rst/nuclear_rst/fuel_bundle.rst similarity index 100% rename from cortix/docs/support_rst/nuclear_rst/fuel_bundle.rst rename to docs/support_rst/nuclear_rst/fuel_bundle.rst diff --git a/cortix/docs/support_rst/nuclear_rst/fuel_segment.rst b/docs/support_rst/nuclear_rst/fuel_segment.rst similarity index 100% rename from cortix/docs/support_rst/nuclear_rst/fuel_segment.rst rename to docs/support_rst/nuclear_rst/fuel_segment.rst diff --git a/cortix/docs/support_rst/nuclear_rst/fuelsegmentsgroups.rst b/docs/support_rst/nuclear_rst/fuelsegmentsgroups.rst similarity index 100% rename from cortix/docs/support_rst/nuclear_rst/fuelsegmentsgroups.rst rename to docs/support_rst/nuclear_rst/fuelsegmentsgroups.rst diff --git a/cortix/docs/support_rst/nuclear_rst/fuelslug.rst b/docs/support_rst/nuclear_rst/fuelslug.rst similarity index 100% rename from cortix/docs/support_rst/nuclear_rst/fuelslug.rst rename to docs/support_rst/nuclear_rst/fuelslug.rst diff --git a/cortix/docs/support_rst/nuclear_rst/modules.rst b/docs/support_rst/nuclear_rst/modules.rst similarity index 100% rename from cortix/docs/support_rst/nuclear_rst/modules.rst rename to docs/support_rst/nuclear_rst/modules.rst diff --git a/cortix/docs/support_rst/nuclear_rst/nuclides.rst b/docs/support_rst/nuclear_rst/nuclides.rst similarity index 100% rename from cortix/docs/support_rst/nuclear_rst/nuclides.rst rename to docs/support_rst/nuclear_rst/nuclides.rst diff --git a/cortix/docs/support_rst/periodictable.rst b/docs/support_rst/periodictable.rst similarity index 100% rename from cortix/docs/support_rst/periodictable.rst rename to docs/support_rst/periodictable.rst diff --git a/cortix/docs/support_rst/phase.rst b/docs/support_rst/phase.rst similarity index 100% rename from cortix/docs/support_rst/phase.rst rename to docs/support_rst/phase.rst diff --git a/cortix/docs/support_rst/phase_new.rst b/docs/support_rst/phase_new.rst similarity index 100% rename from cortix/docs/support_rst/phase_new.rst rename to docs/support_rst/phase_new.rst diff --git a/cortix/docs/support_rst/phase_newest.rst b/docs/support_rst/phase_newest.rst similarity index 100% rename from cortix/docs/support_rst/phase_newest.rst rename to docs/support_rst/phase_newest.rst diff --git a/cortix/docs/support_rst/quantity.rst b/docs/support_rst/quantity.rst similarity index 100% rename from cortix/docs/support_rst/quantity.rst rename to docs/support_rst/quantity.rst diff --git a/cortix/docs/support_rst/specie.rst b/docs/support_rst/specie.rst similarity index 100% rename from cortix/docs/support_rst/specie.rst rename to docs/support_rst/specie.rst diff --git a/cortix/docs/support_rst/species.rst b/docs/support_rst/species.rst similarity index 100% rename from cortix/docs/support_rst/species.rst rename to docs/support_rst/species.rst diff --git a/cortix/docs/support_rst/stream.rst b/docs/support_rst/stream.rst similarity index 100% rename from cortix/docs/support_rst/stream.rst rename to docs/support_rst/stream.rst From cee0feab92ff36494f61296309cdb63e03a97674 Mon Sep 17 00:00:00 2001 From: Uriel Acioli Date: Thu, 12 Sep 2024 05:15:26 -0300 Subject: [PATCH 3/9] refactor(cortix/examples): move cortix's examples to its own folder in the project root --- {cortix/examples => examples}/__init__.py | 0 {cortix/examples => examples}/bwr/condenser.py | 0 {cortix/examples => examples}/bwr/condenser_tester.py | 0 {cortix/examples => examples}/bwr/cooler.py | 0 {cortix/examples => examples}/bwr/params.py | 0 {cortix/examples => examples}/bwr/plant_test.py | 0 {cortix/examples => examples}/bwr/reactor-old.py | 0 {cortix/examples => examples}/bwr/reactor.py | 0 {cortix/examples => examples}/bwr/run_plant.py | 0 {cortix/examples => examples}/bwr/run_reactor.py | 0 {cortix/examples => examples}/bwr/turbine.py | 0 {cortix/examples => examples}/city_justice/adjudication.py | 0 {cortix/examples => examples}/city_justice/arrested.py | 0 {cortix/examples => examples}/city_justice/community.py | 0 {cortix/examples => examples}/city_justice/jail.py | 0 {cortix/examples => examples}/city_justice/parole.py | 0 {cortix/examples => examples}/city_justice/prison.py | 0 {cortix/examples => examples}/city_justice/probation.py | 0 {cortix/examples => examples}/city_justice/run_city_justice.py | 0 {cortix/examples => examples}/droplet_swirl/droplet.py | 0 {cortix/examples => examples}/droplet_swirl/run_droplet_swirl.py | 0 {cortix/examples => examples}/droplet_swirl/vortex.py | 0 {cortix/examples => examples}/ideal_gas/particle.py | 0 {cortix/examples => examples}/ideal_gas/particle_handler.py | 0 {cortix/examples => examples}/ideal_gas/particle_plotting.py | 0 {cortix/examples => examples}/ideal_gas/run_particle.py | 0 {cortix/examples => examples}/nbody/body.py | 0 {cortix/examples => examples}/nbody/run_planets.py | 0 {cortix/examples => examples}/umlrr/code.py | 0 {cortix/examples => examples}/umlrr/run_umlrr.py | 0 {cortix/examples => examples}/umlrr/umlrr.py | 0 31 files changed, 0 insertions(+), 0 deletions(-) rename {cortix/examples => examples}/__init__.py (100%) rename {cortix/examples => examples}/bwr/condenser.py (100%) rename {cortix/examples => examples}/bwr/condenser_tester.py (100%) rename {cortix/examples => examples}/bwr/cooler.py (100%) rename {cortix/examples => examples}/bwr/params.py (100%) rename {cortix/examples => examples}/bwr/plant_test.py (100%) rename {cortix/examples => examples}/bwr/reactor-old.py (100%) rename {cortix/examples => examples}/bwr/reactor.py (100%) rename {cortix/examples => examples}/bwr/run_plant.py (100%) rename {cortix/examples => examples}/bwr/run_reactor.py (100%) rename {cortix/examples => examples}/bwr/turbine.py (100%) rename {cortix/examples => examples}/city_justice/adjudication.py (100%) rename {cortix/examples => examples}/city_justice/arrested.py (100%) rename {cortix/examples => examples}/city_justice/community.py (100%) rename {cortix/examples => examples}/city_justice/jail.py (100%) rename {cortix/examples => examples}/city_justice/parole.py (100%) rename {cortix/examples => examples}/city_justice/prison.py (100%) rename {cortix/examples => examples}/city_justice/probation.py (100%) rename {cortix/examples => examples}/city_justice/run_city_justice.py (100%) rename {cortix/examples => examples}/droplet_swirl/droplet.py (100%) rename {cortix/examples => examples}/droplet_swirl/run_droplet_swirl.py (100%) rename {cortix/examples => examples}/droplet_swirl/vortex.py (100%) rename {cortix/examples => examples}/ideal_gas/particle.py (100%) rename {cortix/examples => examples}/ideal_gas/particle_handler.py (100%) rename {cortix/examples => examples}/ideal_gas/particle_plotting.py (100%) rename {cortix/examples => examples}/ideal_gas/run_particle.py (100%) rename {cortix/examples => examples}/nbody/body.py (100%) rename {cortix/examples => examples}/nbody/run_planets.py (100%) rename {cortix/examples => examples}/umlrr/code.py (100%) rename {cortix/examples => examples}/umlrr/run_umlrr.py (100%) rename {cortix/examples => examples}/umlrr/umlrr.py (100%) diff --git a/cortix/examples/__init__.py b/examples/__init__.py similarity index 100% rename from cortix/examples/__init__.py rename to examples/__init__.py diff --git a/cortix/examples/bwr/condenser.py b/examples/bwr/condenser.py similarity index 100% rename from cortix/examples/bwr/condenser.py rename to examples/bwr/condenser.py diff --git a/cortix/examples/bwr/condenser_tester.py b/examples/bwr/condenser_tester.py similarity index 100% rename from cortix/examples/bwr/condenser_tester.py rename to examples/bwr/condenser_tester.py diff --git a/cortix/examples/bwr/cooler.py b/examples/bwr/cooler.py similarity index 100% rename from cortix/examples/bwr/cooler.py rename to examples/bwr/cooler.py diff --git a/cortix/examples/bwr/params.py b/examples/bwr/params.py similarity index 100% rename from cortix/examples/bwr/params.py rename to examples/bwr/params.py diff --git a/cortix/examples/bwr/plant_test.py b/examples/bwr/plant_test.py similarity index 100% rename from cortix/examples/bwr/plant_test.py rename to examples/bwr/plant_test.py diff --git a/cortix/examples/bwr/reactor-old.py b/examples/bwr/reactor-old.py similarity index 100% rename from cortix/examples/bwr/reactor-old.py rename to examples/bwr/reactor-old.py diff --git a/cortix/examples/bwr/reactor.py b/examples/bwr/reactor.py similarity index 100% rename from cortix/examples/bwr/reactor.py rename to examples/bwr/reactor.py diff --git a/cortix/examples/bwr/run_plant.py b/examples/bwr/run_plant.py similarity index 100% rename from cortix/examples/bwr/run_plant.py rename to examples/bwr/run_plant.py diff --git a/cortix/examples/bwr/run_reactor.py b/examples/bwr/run_reactor.py similarity index 100% rename from cortix/examples/bwr/run_reactor.py rename to examples/bwr/run_reactor.py diff --git a/cortix/examples/bwr/turbine.py b/examples/bwr/turbine.py similarity index 100% rename from cortix/examples/bwr/turbine.py rename to examples/bwr/turbine.py diff --git a/cortix/examples/city_justice/adjudication.py b/examples/city_justice/adjudication.py similarity index 100% rename from cortix/examples/city_justice/adjudication.py rename to examples/city_justice/adjudication.py diff --git a/cortix/examples/city_justice/arrested.py b/examples/city_justice/arrested.py similarity index 100% rename from cortix/examples/city_justice/arrested.py rename to examples/city_justice/arrested.py diff --git a/cortix/examples/city_justice/community.py b/examples/city_justice/community.py similarity index 100% rename from cortix/examples/city_justice/community.py rename to examples/city_justice/community.py diff --git a/cortix/examples/city_justice/jail.py b/examples/city_justice/jail.py similarity index 100% rename from cortix/examples/city_justice/jail.py rename to examples/city_justice/jail.py diff --git a/cortix/examples/city_justice/parole.py b/examples/city_justice/parole.py similarity index 100% rename from cortix/examples/city_justice/parole.py rename to examples/city_justice/parole.py diff --git a/cortix/examples/city_justice/prison.py b/examples/city_justice/prison.py similarity index 100% rename from cortix/examples/city_justice/prison.py rename to examples/city_justice/prison.py diff --git a/cortix/examples/city_justice/probation.py b/examples/city_justice/probation.py similarity index 100% rename from cortix/examples/city_justice/probation.py rename to examples/city_justice/probation.py diff --git a/cortix/examples/city_justice/run_city_justice.py b/examples/city_justice/run_city_justice.py similarity index 100% rename from cortix/examples/city_justice/run_city_justice.py rename to examples/city_justice/run_city_justice.py diff --git a/cortix/examples/droplet_swirl/droplet.py b/examples/droplet_swirl/droplet.py similarity index 100% rename from cortix/examples/droplet_swirl/droplet.py rename to examples/droplet_swirl/droplet.py diff --git a/cortix/examples/droplet_swirl/run_droplet_swirl.py b/examples/droplet_swirl/run_droplet_swirl.py similarity index 100% rename from cortix/examples/droplet_swirl/run_droplet_swirl.py rename to examples/droplet_swirl/run_droplet_swirl.py diff --git a/cortix/examples/droplet_swirl/vortex.py b/examples/droplet_swirl/vortex.py similarity index 100% rename from cortix/examples/droplet_swirl/vortex.py rename to examples/droplet_swirl/vortex.py diff --git a/cortix/examples/ideal_gas/particle.py b/examples/ideal_gas/particle.py similarity index 100% rename from cortix/examples/ideal_gas/particle.py rename to examples/ideal_gas/particle.py diff --git a/cortix/examples/ideal_gas/particle_handler.py b/examples/ideal_gas/particle_handler.py similarity index 100% rename from cortix/examples/ideal_gas/particle_handler.py rename to examples/ideal_gas/particle_handler.py diff --git a/cortix/examples/ideal_gas/particle_plotting.py b/examples/ideal_gas/particle_plotting.py similarity index 100% rename from cortix/examples/ideal_gas/particle_plotting.py rename to examples/ideal_gas/particle_plotting.py diff --git a/cortix/examples/ideal_gas/run_particle.py b/examples/ideal_gas/run_particle.py similarity index 100% rename from cortix/examples/ideal_gas/run_particle.py rename to examples/ideal_gas/run_particle.py diff --git a/cortix/examples/nbody/body.py b/examples/nbody/body.py similarity index 100% rename from cortix/examples/nbody/body.py rename to examples/nbody/body.py diff --git a/cortix/examples/nbody/run_planets.py b/examples/nbody/run_planets.py similarity index 100% rename from cortix/examples/nbody/run_planets.py rename to examples/nbody/run_planets.py diff --git a/cortix/examples/umlrr/code.py b/examples/umlrr/code.py similarity index 100% rename from cortix/examples/umlrr/code.py rename to examples/umlrr/code.py diff --git a/cortix/examples/umlrr/run_umlrr.py b/examples/umlrr/run_umlrr.py similarity index 100% rename from cortix/examples/umlrr/run_umlrr.py rename to examples/umlrr/run_umlrr.py diff --git a/cortix/examples/umlrr/umlrr.py b/examples/umlrr/umlrr.py similarity index 100% rename from cortix/examples/umlrr/umlrr.py rename to examples/umlrr/umlrr.py From 52d08a6ac05c58f52022315205c8c74afbe7c891 Mon Sep 17 00:00:00 2001 From: Uriel Acioli Date: Thu, 12 Sep 2024 05:16:30 -0300 Subject: [PATCH 4/9] refactor(cortix/tests): move cortix's tests to its own folder in the project's root --- tests/__init__.py | 58 +++++++++++++++++ {cortix/tests => tests}/dataplot.py | 45 ++++++------- {cortix/tests => tests}/dummy_module.py | 9 ++- {cortix/tests => tests}/mpi_test_send_recv.py | 9 +-- .../tests => tests}/test_cortix_add_module.py | 6 +- {cortix/tests => tests}/test_data_plot.py | 15 +++-- {cortix/tests => tests}/test_droplet.py | 65 ++++++++++--------- {cortix/tests => tests}/test_module_init.py | 11 ++-- {cortix/tests => tests}/test_send_recv.py | 4 +- 9 files changed, 143 insertions(+), 79 deletions(-) create mode 100644 tests/__init__.py rename {cortix/tests => tests}/dataplot.py (76%) rename {cortix/tests => tests}/dummy_module.py (89%) rename {cortix/tests => tests}/mpi_test_send_recv.py (76%) mode change 100755 => 100644 rename {cortix/tests => tests}/test_cortix_add_module.py (89%) rename {cortix/tests => tests}/test_data_plot.py (85%) rename {cortix/tests => tests}/test_droplet.py (72%) rename {cortix/tests => tests}/test_module_init.py (60%) rename {cortix/tests => tests}/test_send_recv.py (54%) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..0e35e696 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,58 @@ +from cortix.module import Module + +import unittest + + +class DummyModule(Module): + def __init__(self): + # Call the Module class constructor + super().__init__() + + def run(self): + # Simulate sending data every second + i = 0 + while i < 10: + sleep(1) + self.send(i, "test-port1") + print("Sent {}!".format(i)) + i += 1 + + print("Finished Sending!") + + +class DummyModule2(Module): + def __init__(self): + # Call the Module class constructor + super().__init__() + + def run(self): + # Simulate receiving data every two seconds + i = 0 + while i < 10: + sleep(1) + + # Receive data from all connected ports + print("Receiving {}!".format(i)) + data = self.recv("test-port2") + + # Extract the data only from test-port1 + print("Received {}!".format(data)) + + assert data == i + i += 1 + + print("Finished Receiving!") + + +class TestModule(unittest.TestCase): + def test_init(self): + m = DummyModule() + p1 = m.get_port("test-port1") + p2 = m.get_port("test-port2") + self.assertEqual( + first=len(m.ports), second=2, msg=f"Ports len {len(m.ports)} ≠ 2" + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cortix/tests/dataplot.py b/tests/dataplot.py similarity index 76% rename from cortix/tests/dataplot.py rename to tests/dataplot.py index d9d984fc..45ae9945 100644 --- a/cortix/tests/dataplot.py +++ b/tests/dataplot.py @@ -6,30 +6,28 @@ import sys import logging from threading import Thread -import pickle -from mpl_toolkits.mplot3d import Axes3D import matplotlib -matplotlib.use('Agg', warn=False) + +matplotlib.use("Agg", warn=False) import matplotlib.pyplot as plt from cortix import Module -class DataPlot(Module): +class DataPlot(Module): def __init__(self): - super().__init__() self.same_axis = False self.dpi = 200 - self.xlabel = 'x' - self.ylabel = 'y' - self.zlabel = 'z' + self.xlabel = "x" + self.ylabel = "y" + self.zlabel = "z" self.title = None - self.log = logging.getLogger('cortix') + self.log = logging.getLogger("cortix") self.debug = False self.print_freq = 10 @@ -38,9 +36,9 @@ def __init__(self): self.state = self.data def run(self, *args): - ''' + """ Spawn a thread to handle each port connection. - ''' + """ threads = [] @@ -57,9 +55,9 @@ def run(self, *args): return def recv_data(self, port): - ''' + """ Keep listening on the port and receiving data. - ''' + """ data = [] @@ -67,26 +65,24 @@ def recv_data(self, port): while True: d = self.recv(port) if self.debug and i % self.print_freq == 0: - self.log.info('DataPlot::'+port.name+' received: {}'.format(d)) + self.log.info("DataPlot::" + port.name + " received: {}".format(d)) if isinstance(d, str) and d == "DONE": self.data[port.name] = data - sys.exit(0) # kill thread + sys.exit(0) # kill thread i += 1 data.append(d) def plot_data(self): - if self.same_axis: fig = plt.figure(1) ax = None - for (key,data) in self.data.items(): - + for key, data in self.data.items(): x = [i[0] for i in data] y = [i[1] for i in data] - # 2D-Plot + # 2D-Plot if data and len(data[0]) == 2: if not self.same_axis: fig = plt.figure(key) @@ -95,11 +91,10 @@ def plot_data(self): plt.title(self.title) plt.plot(x, y) - # 3D-Plot + # 3D-Plot elif data and len(data[0]) == 3: - if self.same_axis and ax is None: - ax = fig.add_subplot(111, projection='3d') + ax = fig.add_subplot(111, projection="3d") ax.set_xlabel(self.xlabel) ax.set_ylabel(self.ylabel) ax.set_zlabel(self.zlabel) @@ -107,7 +102,7 @@ def plot_data(self): if not self.same_axis: fig = plt.figure(key) - ax = fig.add_subplot(111, projection='3d') + ax = fig.add_subplot(111, projection="3d") ax.set_xlabel(self.xlabel) ax.set_ylabel(self.ylabel) ax.set_zlabel(self.zlabel) @@ -116,7 +111,7 @@ def plot_data(self): ax.plot(x, y, [i[2] for i in data]) if not self.same_axis: - plt.savefig('{}.png'.format(key), dpi=self.dpi) + plt.savefig("{}.png".format(key), dpi=self.dpi) if self.same_axis: - plt.savefig('{}.png'.format(key.split(':')[0]), dpi=self.dpi) + plt.savefig("{}.png".format(key.split(":")[0]), dpi=self.dpi) diff --git a/cortix/tests/dummy_module.py b/tests/dummy_module.py similarity index 89% rename from cortix/tests/dummy_module.py rename to tests/dummy_module.py index 14d1bace..20556e5c 100644 --- a/cortix/tests/dummy_module.py +++ b/tests/dummy_module.py @@ -1,10 +1,9 @@ from cortix import Module -from cortix import Port from time import sleep + class DummyModule(Module): def __init__(self): - # Call the Module class constructor super().__init__() @@ -19,14 +18,14 @@ def run(self): print("Finished Sending!") + class DummyModule2(Module): def __init__(self): - # Call the Module class constructor super().__init__() def run(self): - # Simulate receiving data every two seconds + # Simulate receiving data every two seconds i = 0 while i < 10: sleep(1) @@ -38,7 +37,7 @@ def run(self): # Extract the data only from test-port1 print("Received {}!".format(data)) - assert(data == i) + assert data == i i += 1 print("Finished Receiving!") diff --git a/cortix/tests/mpi_test_send_recv.py b/tests/mpi_test_send_recv.py old mode 100755 new mode 100644 similarity index 76% rename from cortix/tests/mpi_test_send_recv.py rename to tests/mpi_test_send_recv.py index 57c0c7d9..3ddee636 --- a/cortix/tests/mpi_test_send_recv.py +++ b/tests/mpi_test_send_recv.py @@ -4,23 +4,24 @@ from cortix.tests.dummy_module import DummyModule from cortix.tests.dummy_module import DummyModule2 -def mpi_send_recv(): +def mpi_send_recv(): c = Cortix() c.network = Network() m1 = DummyModule() c.network.add_module(m1) - p1 = m1.get_port('test-port1') + p1 = m1.get_port("test-port1") m2 = DummyModule2() c.network.add_module(m2) - p2 = m2.get_port('test-port2') + p2 = m2.get_port("test-port2") - c.network.connect([m1,p1],[m2,p2],'bidirectional') + c.network.connect([m1, p1], [m2, p2], "bidirectional") c.run() + if __name__ == "__main__": mpi_send_recv() diff --git a/cortix/tests/test_cortix_add_module.py b/tests/test_cortix_add_module.py similarity index 89% rename from cortix/tests/test_cortix_add_module.py rename to tests/test_cortix_add_module.py index 32a7bddf..c9ce9ff8 100755 --- a/cortix/tests/test_cortix_add_module.py +++ b/tests/test_cortix_add_module.py @@ -6,6 +6,7 @@ from cortix.tests.dummy_module import DummyModule + def test_cortix_add_module(): # Init the Cortix object c = Cortix() @@ -21,8 +22,8 @@ def test_cortix_add_module(): c.network.add_module(m) # Get ports - p1 = m.get_port('test1-{}'.format(i)) - p2 = m.get_port('test2-{}'.format(i)) + p1 = m.get_port("test1-{}".format(i)) + p2 = m.get_port("test2-{}".format(i)) # Make sure we have the correct modules assert len(c.network.modules) == num_modules @@ -30,5 +31,6 @@ def test_cortix_add_module(): assert isinstance(mod, Module) assert len(mod.ports) == 2 + if __name__ == "__main__": test_cortix_add_module() diff --git a/cortix/tests/test_data_plot.py b/tests/test_data_plot.py similarity index 85% rename from cortix/tests/test_data_plot.py rename to tests/test_data_plot.py index c7c26c22..cf7c0b43 100755 --- a/cortix/tests/test_data_plot.py +++ b/tests/test_data_plot.py @@ -9,6 +9,7 @@ from cortix.tests.dataplot import DataPlot + class PlotData(Module): def __init__(self): super().__init__() @@ -23,17 +24,17 @@ def run(self): self.send("DONE", "plot-out") print("Finished sending!") -if __name__ == "__main__": +if __name__ == "__main__": # Cortix built-in DataPlot module d = DataPlot() - #d.set_title("Test Plot") - #d.set_xlabel("Time") - #d.set_ylabel("Position") + # d.set_title("Test Plot") + # d.set_xlabel("Time") + # d.set_ylabel("Position") - d.title = 'Test Plot' - d.xlabel = 'Time' - d.ylabel = 'Position' + d.title = "Test Plot" + d.xlabel = "Time" + d.ylabel = "Position" # Custom class to send dummy data p = PlotData() diff --git a/cortix/tests/test_droplet.py b/tests/test_droplet.py similarity index 72% rename from cortix/tests/test_droplet.py rename to tests/test_droplet.py index e9062326..b7ec5b51 100755 --- a/cortix/tests/test_droplet.py +++ b/tests/test_droplet.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # This file is part of the Cortix toolkit environment # https://cortix.org -''' +""" This example uses three modules instantiated many times in two different networks. Each network configuration uses a different amount of module instances and a different network topology. This example can be executed with MPI (if mpi4py is available) or @@ -44,29 +44,28 @@ command line as `run_droplet.py` -''' +""" import scipy.constants as const from cortix import Cortix from cortix import Network -from cortix.tests.dataplot import DataPlot -from cortix.examples.droplet_swirl.droplet import Droplet -from cortix.examples.droplet_swirl.vortex import Vortex - -if __name__ == '__main__': +from tests.dataplot import DataPlot +from examples.droplet_swirl.droplet import Droplet +from examples.droplet_swirl.vortex import Vortex +if __name__ == "__main__": # Configuration Parameters - use_single_plot = True # True for a single plot output - # False for multiple plot files and network - use_mpi = False # True for MPI; False for Python multiprocessing + use_single_plot = True # True for a single plot output + # False for multiple plot files and network + use_mpi = False # True for MPI; False for Python multiprocessing - plot_vortex_profile = False # True may crash the X server. + plot_vortex_profile = False # True may crash the X server. n_droplets = 10 - end_time = 3*const.minute - time_step = 0.2 + end_time = 3 * const.minute + time_step = 0.2 cortix = Cortix(use_mpi=use_mpi, splash=True) cortix_net = Network() @@ -74,11 +73,10 @@ # Network for a single plot case if use_single_plot: - # Vortex module (single). vortex = Vortex() cortix_net.add_module(vortex) - vortex.show_time = (True,1*const.minute) + vortex.show_time = (True, 1 * const.minute) vortex.end_time = end_time vortex.time_step = time_step if plot_vortex_profile: @@ -87,12 +85,11 @@ # DataPlot module (single). data_plot = DataPlot() cortix_net.add_module(data_plot) - data_plot.title = 'Droplet Trajectories' + data_plot.title = "Droplet Trajectories" data_plot.same_axis = True data_plot.dpi = 300 for i in range(n_droplets): - # Droplet modules (multiple). droplet = Droplet() cortix_net.add_module(droplet) @@ -102,26 +99,28 @@ droplet.slip = False # Network port connectivity (connect modules through their ports) - cortix_net.connect( [droplet,'external-flow'], - [vortex, vortex.get_port('fluid-flow:{}'.format(i))], - 'bidirectional') - cortix_net.connect( [droplet,'visualization'], - [data_plot, data_plot.get_port('viz-data:{:05}'.format(i))] ) + cortix_net.connect( + [droplet, "external-flow"], + [vortex, vortex.get_port("fluid-flow:{}".format(i))], + "bidirectional", + ) + cortix_net.connect( + [droplet, "visualization"], + [data_plot, data_plot.get_port("viz-data:{:05}".format(i))], + ) # Network for a multiple plot case if not use_single_plot: - # Vortex module (single). vortex = Vortex() cortix_net.add_module(vortex) - vortex.show_time = (True,100) + vortex.show_time = (True, 100) vortex.end_time = end_time vortex.time_step = time_step if plot_vortex_profile: vortex.plot_velocity() for i in range(n_droplets): - # Droplet modules (multiple). droplet = Droplet() cortix_net.add_module(droplet) @@ -133,15 +132,19 @@ # DataPlot modules (multiple). data_plot = DataPlot() cortix_net.add_module(data_plot) - data_plot.title = 'Droplet Trajectory '+str(i) + data_plot.title = "Droplet Trajectory " + str(i) data_plot.dpi = 300 # Network port connectivity (connect modules through their ports) - cortix_net.connect( [droplet,'external-flow'], - [vortex, vortex.get_port('fluid-flow:{}'.format(i))], - 'bidirectional') - cortix_net.connect( [droplet,'visualization'], - [data_plot, data_plot.get_port('viz-data:{:05}'.format(i))] ) + cortix_net.connect( + [droplet, "external-flow"], + [vortex, vortex.get_port("fluid-flow:{}".format(i))], + "bidirectional", + ) + cortix_net.connect( + [droplet, "visualization"], + [data_plot, data_plot.get_port("viz-data:{:05}".format(i))], + ) cortix_net.draw() diff --git a/cortix/tests/test_module_init.py b/tests/test_module_init.py similarity index 60% rename from cortix/tests/test_module_init.py rename to tests/test_module_init.py index 30a484e7..c2021cd1 100755 --- a/cortix/tests/test_module_init.py +++ b/tests/test_module_init.py @@ -1,18 +1,21 @@ #!/usr/bin/env python -from cortix.tests.dummy_module import DummyModule + +from tests.dummy_module import DummyModule + def test_module_init(): # Initialize the module m = DummyModule() # get ports - p1 = m.get_port('test-1') - p2 = m.get_port('test-2') + p1 = m.get_port("test-1") + p2 = m.get_port("test-2") # Output the list of ports print(m.ports) - assert(len(m.ports) == 2) + assert len(m.ports) == 2 + if __name__ == "__main__": test_module_init() diff --git a/cortix/tests/test_send_recv.py b/tests/test_send_recv.py similarity index 54% rename from cortix/tests/test_send_recv.py rename to tests/test_send_recv.py index ec74441d..ef7342e9 100755 --- a/cortix/tests/test_send_recv.py +++ b/tests/test_send_recv.py @@ -2,8 +2,10 @@ import os + def test_send_recv(): - assert(os.system("mpirun --oversubscribe -np 3 python mpi_test_send_recv.py") == 0) + assert os.system("mpirun --oversubscribe -np 3 python mpi_test_send_recv.py") == 0 + if __name__ == "__main__": test_send_recv() From e3ccfdf858d8c6d96b45c851986b4660b2b40f87 Mon Sep 17 00:00:00 2001 From: Uriel Acioli Date: Thu, 12 Sep 2024 05:17:51 -0300 Subject: [PATCH 5/9] chore(.gitignore): update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c3409f78..9447f695 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ cortix/docs/_build # Coverage files .coverage coverage.xml +.pdm-python +.ruff_cache +pdm.lock From 435c825d0c717dc65343e91b35e2a22391b87147 Mon Sep 17 00:00:00 2001 From: Uriel Acioli Date: Thu, 12 Sep 2024 05:36:02 -0300 Subject: [PATCH 6/9] feat(pyproject.toml): add dependencies' versions --- pyproject.toml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6ad7fb9c..ecd0d328 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,13 +21,12 @@ classifiers = [ 'Topic :: Utilities', ] dependencies = [ - "pandas", - "matplotlib", - "numpy", - "scipy", - "pytest", - "graphviz", - "networkx>=3.3", + "pandas>=2.2.2", + "matplotlib>=3.9.2", + "numpy>=2.1.1", + "scipy>=1.14.1", + "graphviz>=0.20.3", + "networkx>=3.3", ] requires-python = "==3.11.*" readme = "README.md" @@ -48,4 +47,4 @@ lint = { cmd = [ format = { cmd = ['ruff', 'format', './tests', './cortix', './examples'] } [tool.pdm.dev-dependencies] -dev = ["ruff>=0.6.4"] +dev = ["ruff>=0.6.4", "pytest>=8.3.3"] From 16d7f28179a5cd23aa5023e882ce229024446870 Mon Sep 17 00:00:00 2001 From: Uriel Acioli Date: Thu, 12 Sep 2024 05:57:06 -0300 Subject: [PATCH 7/9] chore(pyproject.toml): update project metadata Add build-system, maintainers, authors sections to pyproject.toml. Add dependency versioning. --- pyproject.toml | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ecd0d328..f83822a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,28 @@ +# https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/#fallback-behaviour +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" + [project] name = "cortix" -version = "1.1.43" description = "Cortix is a Python library for network dynamics modeling and HPC simulation." -authors = [ +authors = [{ name = "Cortix Computing", email = "valmor_dealmeida@uml.edu" }] +maintainers = [ { name = "Valmor F. de Almeida", email = "valmor_dealmeida@uml.edu" }, + { name = "Uriel Acioli", email = "uriel.acioli@gmail.com" }, +] +version = "1.1.43" +requires-python = "==3.11.*" +license = { file = "LICENSE.txt" } +readme = "README.md" +dependencies = [ + "pandas>=2.2.2", + "matplotlib>=3.9.2", + "numpy>=2.1.1", + "scipy>=1.14.1", + "graphviz>=0.20.3", + "networkx>=3.3", ] -maintainers = [{ name = "Uriel Acioli", email = "uriel.acioli@gmail.com" }] keywords = ['simulation', 'math'] classifiers = [ "Programming Language :: Python :: 3", @@ -20,18 +37,7 @@ classifiers = [ 'Topic :: Education', 'Topic :: Utilities', ] -dependencies = [ - "pandas>=2.2.2", - "matplotlib>=3.9.2", - "numpy>=2.1.1", - "scipy>=1.14.1", - "graphviz>=0.20.3", - "networkx>=3.3", -] -requires-python = "==3.11.*" -readme = "README.md" -license = { file = "LICENSE.txt" } - +urls = { homepage = "https://cortix.org" } [tool.pdm] distribution = false From c1f814aa8a62dc140879f9d85de1223531ad2079 Mon Sep 17 00:00:00 2001 From: Uriel Acioli Date: Thu, 12 Sep 2024 06:09:19 -0300 Subject: [PATCH 8/9] refactor(cortix/src): move cortix's src to its own folder in the project's root --- cortix/__init__.py | 8 +- cortix/{src => }/cortix_main.py | 110 +- cortix/{src => }/module.py | 61 +- cortix/{src => }/network.py | 175 +- cortix/node.py | 17 + cortix/{src => }/port.py | 17 +- cortix/src/node.py | 17 - cortix/support/chemeng/reaction_mechanism.py | 2400 +++++--- cortix/support/nuclear/actor.py | 66 +- cortix/support/nuclear/fuel_bucket.py | 394 +- cortix/support/nuclear/fuel_bundle.py | 243 +- cortix/support/nuclear/fuel_segment.py | 476 +- cortix/support/nuclear/fuelsegmentsgroups.py | 173 +- cortix/support/nuclear/fuelslug.py | 812 +-- cortix/support/nuclear/nuclides.py | 457 +- cortix/support/nuclear/target_rod.py | 17 +- cortix/support/periodictable.py | 5749 ++++++++++++------ cortix/support/phase.py | 419 +- cortix/support/phase_new.py | 611 +- cortix/support/phase_newest.py | 588 +- cortix/support/quantity.py | 245 +- cortix/support/specie.py | 477 +- cortix/support/species.py | 417 +- cortix/support/stream.py | 98 +- cortix/support/units.py | 50 +- 25 files changed, 8668 insertions(+), 5429 deletions(-) rename cortix/{src => }/cortix_main.py (63%) rename cortix/{src => }/module.py (83%) rename cortix/{src => }/network.py (74%) create mode 100644 cortix/node.py rename cortix/{src => }/port.py (92%) delete mode 100644 cortix/src/node.py diff --git a/cortix/__init__.py b/cortix/__init__.py index 398550b0..b1bbd175 100644 --- a/cortix/__init__.py +++ b/cortix/__init__.py @@ -3,11 +3,11 @@ # This file is part of the Cortix toolkit environment # https://cortix.org -from .src.cortix_main import Cortix +from .cortix_main import Cortix -from .src.network import Network -from .src.module import Module -from .src.port import Port +from .network import Network +from .module import Module +from .port import Port from .support.units import Units from .support.phase import Phase diff --git a/cortix/src/cortix_main.py b/cortix/cortix_main.py similarity index 63% rename from cortix/src/cortix_main.py rename to cortix/cortix_main.py index 7ca70b41..5d050136 100644 --- a/cortix/src/cortix_main.py +++ b/cortix/cortix_main.py @@ -9,7 +9,8 @@ import time import datetime -from cortix.src.network import Network +from .network import Network + class Cortix: """Cortix main class definition. @@ -22,7 +23,7 @@ class Cortix: """ - def __init__(self, use_mpi=False, splash=False, log_filename_stem='cortix'): + def __init__(self, use_mpi=False, splash=False, log_filename_stem="cortix"): """Construct a Cortix simulation object. Parameters @@ -64,6 +65,7 @@ def __init__(self, use_mpi=False, splash=False, log_filename_stem='cortix'): if self.use_mpi: try: from mpi4py import MPI + self.comm = MPI.COMM_WORLD self.rank = self.comm.Get_rank() self.size = self.comm.size @@ -75,19 +77,18 @@ def __init__(self, use_mpi=False, splash=False, log_filename_stem='cortix'): # Done if self.rank == 0 or self.use_multiprocessing: - if self.splash: - self.log.info('Created Cortix object %s', self.__get_splash(begin=True)) + self.log.info("Created Cortix object %s", self.__get_splash(begin=True)) else: - self.log.info('Created Cortix object') + self.log.info("Created Cortix object") # Initialize all date and timings self.wall_clock_time_start = time.time() self.wall_clock_time_end = self.wall_clock_time_start - self.end_run_date = datetime.datetime.today().strftime('%d%b%y %H:%M:%S') + self.end_run_date = datetime.datetime.today().strftime("%d%b%y %H:%M:%S") # Delete any existing .ctx-saved/* - shutil.rmtree('.ctx-saved', ignore_errors=True) + shutil.rmtree(".ctx-saved", ignore_errors=True) def __set_network(self, n): assert isinstance(n, Network) @@ -97,20 +98,23 @@ def __set_network(self, n): n.size = self.size n.comm = self.comm self.__network = n + def __get_network(self): return self.__network + network = property(__get_network, __set_network, None, None) def run(self, save=False): - """Run the Cortix network simulation. - """ + """Run the Cortix network simulation.""" self.__network._Network__run(save) if self.rank == 0 or self.use_multiprocessing: self.wall_clock_time_end = time.time() - self.log.info('run()::Elapsed wall clock time [s]: '+ - str(round(self.wall_clock_time_end-self.wall_clock_time_start, 2))) + self.log.info( + "run()::Elapsed wall clock time [s]: " + + str(round(self.wall_clock_time_end - self.wall_clock_time_start, 2)) + ) def close(self): """Closes the cortix object properly before destruction. @@ -124,16 +128,17 @@ def close(self): self.comm.Barrier() if self.rank == 0 or self.use_multiprocessing: - if self.splash: - self.log.info('Closed Cortix object.'+self.__get_splash(end=True)) + self.log.info("Closed Cortix object." + self.__get_splash(end=True)) else: - self.log.info('Closed Cortix object.') + self.log.info("Closed Cortix object.") self.wall_clock_time_end = time.time() - self.log.info('close()::Elapsed wall clock time [s]: '+ - str(round(self.wall_clock_time_end-self.wall_clock_time_start, 2))) + self.log.info( + "close()::Elapsed wall clock time [s]: " + + str(round(self.wall_clock_time_end - self.wall_clock_time_start, 2)) + ) logging.shutdown() return @@ -143,8 +148,8 @@ def __create_logger(self): # File removal if self.rank == 0 or self.use_multiprocessing: - if os.path.isfile(self.log_filename_stem+'.log'): - os.remove(self.log_filename_stem+'.log') + if os.path.isfile(self.log_filename_stem + ".log"): + os.remove(self.log_filename_stem + ".log") # Sync here to allow for file removal if self.use_mpi: @@ -153,9 +158,9 @@ def __create_logger(self): self.log = logging.getLogger(self.log_filename_stem) self.log.setLevel(logging.DEBUG) - #10/8/19 Added check to see if log hander exsists before creating new ones + # 10/8/19 Added check to see if log hander exsists before creating new ones if not self.log.hasHandlers(): - file_handler = logging.FileHandler(self.log_filename_stem+'.log') + file_handler = logging.FileHandler(self.log_filename_stem + ".log") file_handler.setLevel(logging.DEBUG) console_handler = logging.StreamHandler() @@ -163,10 +168,13 @@ def __create_logger(self): # Formatter added to handlers if self.use_mpi: - fs = '[rank:{}] %(asctime)s - %(name)s - %(levelname)s - %(message)s'.format(self.rank) + fs = "[rank:{}] %(asctime)s - %(name)s - %(levelname)s - %(message)s".format( + self.rank + ) else: - - fs = "[{}] %(asctime)s - %(name)s - %(levelname)s - %(message)s".format(os.getpid()) + fs = "[{}] %(asctime)s - %(name)s - %(levelname)s - %(message)s".format( + os.getpid() + ) formatter = logging.Formatter(fs) file_handler.setFormatter(formatter) @@ -179,7 +187,7 @@ def __create_logger(self): return def __get_splash(self, begin=None, end=None): - '''Returns the Cortix splash logo. + """Returns the Cortix splash logo. Note ---- @@ -196,7 +204,7 @@ def __get_splash(self, begin=None, end=None): splash: str The Cortix splash logo. - ''' + """ assert begin is None or end is None if begin: @@ -204,33 +212,36 @@ def __get_splash(self, begin=None, end=None): elif end: begin = False - - splash = \ - '_____________________________________________________________________________\n'+\ - ' ... s . (TAAG Fraktur)\n'+\ - ' xH88"`~ .x8X :8 @88>\n'+\ - ' :8888 .f"8888Hf u. .u . .88 %8P uL ..\n'+\ - ':8888> X8L ^""` ...ue888b .d88B :@8c :888ooo . .@88b @88R\n'+\ - 'X8888 X888h 888R Y888r ="8888f8888r -*8888888 .@88u ""Y888k/"*P\n'+\ - '88888 !88888. 888R I888> 4888>"88" 8888 ''888E` Y888L\n'+\ - '88888 %88888 888R I888> 4888> " 8888 888E 8888\n'+\ - '88888 `> `8888> 888R I888> 4888> 8888 888E `888N\n'+\ - '`8888L % ?888 ! u8888cJ888 .d888L .+ .8888Lu= 888E .u./"888&\n'+\ - ' `8888 `-*"" / "*888*P" ^"8888*" ^%888* 888& d888" Y888*"\n'+\ - ' "888. :" "Y" "Y" "Y" R888" ` "Y Y"\n'+\ - ' `""***~"` ""\n'+\ - ' https://cortix.org \n'+\ - '_____________________________________________________________________________' + splash = ( + "_____________________________________________________________________________\n" + + " ... s . (TAAG Fraktur)\n" + + ' xH88"`~ .x8X :8 @88>\n' + + ' :8888 .f"8888Hf u. .u . .88 %8P uL ..\n' + + ':8888> X8L ^""` ...ue888b .d88B :@8c :888ooo . .@88b @88R\n' + + 'X8888 X888h 888R Y888r ="8888f8888r -*8888888 .@88u ""Y888k/"*P\n' + + '88888 !88888. 888R I888> 4888>"88" 8888 ' + "888E` Y888L\n" + + '88888 %88888 888R I888> 4888> " 8888 888E 8888\n' + + "88888 `> `8888> 888R I888> 4888> 8888 888E `888N\n" + + '`8888L % ?888 ! u8888cJ888 .d888L .+ .8888Lu= 888E .u./"888&\n' + + ' `8888 `-*"" / "*888*P" ^"8888*" ^%888* 888& d888" Y888*"\n' + + ' "888. :" "Y" "Y" "Y" R888" ` "Y Y"\n' + + ' `""***~"` ""\n' + + " https://cortix.org \n" + + "_____________________________________________________________________________" + ) if begin: - message = \ - '\n_____________________________________________________________________________\n'+\ - ' L A U N C H I N G \n' + message = ( + "\n_____________________________________________________________________________\n" + + " L A U N C H I N G \n" + ) else: - message = \ - '\n_____________________________________________________________________________\n'+\ - ' T E R M I N A T I N G \n' + message = ( + "\n_____________________________________________________________________________\n" + + " T E R M I N A T I N G \n" + ) return message + splash @@ -247,5 +258,6 @@ def __del__(self): pass -if __name__ == '__main__': + +if __name__ == "__main__": c = Cortix() diff --git a/cortix/src/module.py b/cortix/module.py similarity index 83% rename from cortix/src/module.py rename to cortix/module.py index dc01b739..23d5ec78 100644 --- a/cortix/src/module.py +++ b/cortix/module.py @@ -6,10 +6,11 @@ import os import pickle import logging -from cortix.src.port import Port +from .port import Port + class Module: - '''Cortix module super class. + """Cortix module super class. This class provides facilities for creating modules within the Cortix network. Cortix will map one object of this class to either a Multiprocessing or MPI @@ -21,10 +22,10 @@ class Module: In order to execute, modules *must* override the `run` method, which will be executed during the simulation. - ''' + """ def __init__(self): - '''Module super class constructor. + """Module super class constructor. Note ---- @@ -58,7 +59,7 @@ def __init__(self): __network: Network An internal network inherited by the derived module for nested networks. - ''' + """ self.name = self.__class__.__name__ self.port_names_expected = None @@ -66,7 +67,7 @@ def __init__(self): self.use_mpi = False self.use_multiprocessing = True self.ports = list() - self.log = logging.getLogger('cortix') + self.log = logging.getLogger("cortix") self.save = False self.id = None @@ -74,7 +75,7 @@ def __init__(self): self.__network = None def send(self, data, port): - '''Send data through a given port. + """Send data through a given port. Parameters ---------- @@ -83,7 +84,7 @@ def send(self, data, port): port: Port, str A Port object to send the data through, or its string name - ''' + """ if isinstance(port, str): port = self.get_port(port) @@ -95,7 +96,7 @@ def send(self, data, port): port.send(data) def recv(self, port): - '''Receive data from a given port + """Receive data from a given port Warning ------- @@ -111,19 +112,19 @@ def recv(self, port): data: any The data received through the port - ''' + """ if isinstance(port, str): port = self.get_port(port) elif isinstance(port, Port): - assert port in self.ports, 'Unknown port!' + assert port in self.ports, "Unknown port!" else: - raise TypeError('port must be of Port or String type') + raise TypeError("port must be of Port or String type") return port.recv() def get_port(self, name): - '''Get port by name; if it does not exist, create one. + """Get port by name; if it does not exist, create one. Parameters ---------- @@ -135,9 +136,9 @@ def get_port(self, name): port: Port The port object with the corresponding name - ''' + """ - assert isinstance(name, str), 'port name must be of type str' + assert isinstance(name, str), "port name must be of type str" port = None for pti in self.ports: @@ -147,8 +148,9 @@ def get_port(self, name): if port is None: if self.port_names_expected: - assert name in self.port_names_expected,\ - 'port name: {}, not expected by module: {}'.format(name, self) + assert ( + name in self.port_names_expected + ), "port name: {}, not expected by module: {}".format(name, self) port = Port(name, self.use_mpi) self.ports.append(port) @@ -156,17 +158,20 @@ def get_port(self, name): def __set_network(self, n): # Must be here to avoid infinite import loop - from cortix.src.network import Network + from cortix.network import Network + assert isinstance(n, Network) n.use_mpi = self.use_mpi n.use_multiprocessing = self.use_multiprocessing self.__network = n + def __get_network(self): return self.__network + network = property(__get_network, __set_network, None, None) def run(self, *args): - '''Module run function + """Module run function Run method with an option to pass data back to the parent process when running in Python multiprocessing mode. If the user does not want to share data with @@ -206,28 +211,30 @@ def run(self, *args): `state_comm.put((idx_comm,self.state))` must be the last command in the method before `return`. In addition, self.state must be `pickle-able`. - ''' - raise NotImplementedError('Module must implement run()') + """ + raise NotImplementedError("Module must implement run()") def run_and_save(self): - self.run() if self.save: - file_name = os.path.join('.ctx-saved', '{}_'.format(self.__class__.__name__)) + file_name = os.path.join( + ".ctx-saved", "{}_".format(self.__class__.__name__) + ) # Import where needed for no broken dependencies if self.use_mpi: from mpi4py import MPI + file_name += str(MPI.COMM_WORLD.rank) else: file_name += str(os.getpid()) - file_name += '.pkl' + file_name += ".pkl" - self.ports = list() # reset ports since they can't be pickled + self.ports = list() # reset ports since they can't be pickled self.log = None try: - with open(file_name, 'wb') as fout: + with open(file_name, "wb") as fout: pickle.dump(self, fout) except pickle.PicklingError: - print('Unable to pickle {}!'.format(file_name)) + print("Unable to pickle {}!".format(file_name)) diff --git a/cortix/src/network.py b/cortix/network.py similarity index 74% rename from cortix/src/network.py rename to cortix/network.py index d23294e7..739ebc12 100644 --- a/cortix/src/network.py +++ b/cortix/network.py @@ -9,8 +9,9 @@ import logging from multiprocessing import Process -from cortix.src.module import Module -from cortix.src.port import Port +from .module import Module +from .port import Port + class Network: """Cortix network. @@ -34,19 +35,19 @@ def __init__(self): root process. This can generate an `out of memory` condition. This variable sets the maximum number of processes for which the data will be copied. Default is 1000. - """ + """ self.id = Network.num_networks - self.name = 'network-'+str(self.id) - self.log = logging.getLogger('cortix') + self.name = "network-" + str(self.id) + self.log = logging.getLogger("cortix") self.max_n_modules_for_data_copy_on_root = 1000 self.modules = list() self.gv_edges = list() - self.gv_info = 'undirectional' + self.gv_info = "undirectional" self.use_mpi = None self.use_multiprocessing = None @@ -55,17 +56,16 @@ def __init__(self): self.size = None self.comm = None - self.save = False # save all network modules + self.save = False # save all network modules Network.num_networks += 1 return def module(self, m): - """Add a module. - """ + """Add a module.""" - assert isinstance(m, Module), 'm must be a module' + assert isinstance(m, Module), "m must be a module" name = m.name if not name: @@ -75,13 +75,12 @@ def module(self, m): m.use_mpi = self.use_mpi m.use_multiprocessing = self.use_multiprocessing self.modules.append(m) - m.id = len(self.modules)-1 # see module doc for module id + m.id = len(self.modules) - 1 # see module doc for module id return def add_module(self, m): - """Alternative name to `module()`. - """ + """Alternative name to `module()`.""" self.module(m) @@ -124,18 +123,17 @@ def connect(self, module_port_a, module_port_b, info=None): if info: assert isinstance(info, str) - assert info in ['undirectional', 'directional', 'bidirectional'] + assert info in ["undirectional", "directional", "bidirectional"] self.gv_info = info if isinstance(module_port_a, Module) and isinstance(module_port_b, Module): - module_a = module_port_a module_b = module_port_b assert module_a.name and module_b.name # sanity check - assert module_a in self.modules, 'module %r not in network.'%module_a.name - assert module_b in self.modules, 'module %r not in network.'%module_b.name + assert module_a in self.modules, "module %r not in network." % module_a.name + assert module_b in self.modules, "module %r not in network." % module_b.name # Connect ports port_a = module_a.get_port(module_b.name.lower()) @@ -150,31 +148,45 @@ def connect(self, module_port_a, module_port_b, info=None): if (str(idx_a), str(idx_b)) not in self.gv_edges: self.gv_edges.append((str(idx_a), str(idx_b))) - if self.gv_info == 'bidirectional' and (str(idx_b), str(idx_a)) not in self.gv_edges: + if ( + self.gv_info == "bidirectional" + and (str(idx_b), str(idx_a)) not in self.gv_edges + ): self.gv_edges.append((str(idx_b), str(idx_a))) elif isinstance(module_port_a, list) and isinstance(module_port_b, list): - - assert len(module_port_a) == 2 and isinstance(module_port_a[0], Module) and\ - (isinstance(module_port_a[1], str) or isinstance(module_port_a[1], Port)) - - assert len(module_port_b) == 2 and isinstance(module_port_b[0], Module) and\ - (isinstance(module_port_b[1], str) or isinstance(module_port_b[1], Port)) + assert ( + len(module_port_a) == 2 + and isinstance(module_port_a[0], Module) + and ( + isinstance(module_port_a[1], str) + or isinstance(module_port_a[1], Port) + ) + ) + + assert ( + len(module_port_b) == 2 + and isinstance(module_port_b[0], Module) + and ( + isinstance(module_port_b[1], str) + or isinstance(module_port_b[1], Port) + ) + ) module_a = module_port_a[0] module_b = module_port_b[0] assert module_a.name and module_b.name # sanity check - assert module_a in self.modules, 'module %r not in network.'%module_a.name - assert module_b in self.modules, 'module %r not in network.'%module_b.name + assert module_a in self.modules, "module %r not in network." % module_a.name + assert module_b in self.modules, "module %r not in network." % module_b.name idx_a = self.modules.index(module_a) idx_b = self.modules.index(module_b) self.gv_edges.append((str(idx_a), str(idx_b))) - if self.gv_info == 'bidirectional': + if self.gv_info == "bidirectional": self.gv_edges.append((str(idx_b), str(idx_a))) if isinstance(module_port_a[1], str): @@ -182,19 +194,19 @@ def connect(self, module_port_a, module_port_b, info=None): elif isinstance(module_port_a[1], Port): port_a = module_port_a[1] else: - assert False, 'help!' + assert False, "help!" if isinstance(module_port_b[1], str): port_b = module_b.get_port(module_port_b[1]) elif isinstance(module_port_b[1], Port): port_b = module_port_b[1] else: - assert False, 'help!' + assert False, "help!" port_a.connect(port_b) else: - assert False, ' not implemented.' + assert False, " not implemented." return @@ -203,7 +215,7 @@ def __run(self, save=False): Internal method to run the network simulation. Do not use this method, it is intended for Cortix to run it. - This function concurrently executes the `cortix.src.module.run` function + This function concurrently executes the `cortix.module.run` function for each module in the network. Modules are run using either MPI or Multiprocessing, depending on the user configuration. @@ -212,27 +224,27 @@ def __run(self, save=False): When using multiprocessing, data from the modules state are copied to the master process after the `__run()` method of the modules is finished. """ - assert len(self.modules) >= 1, 'the network must have a list of modules.' + assert len(self.modules) >= 1, "the network must have a list of modules." # Create directory for saving modules states if self.rank == 0 or self.use_multiprocessing: - shutil.rmtree('.ctx-saved', ignore_errors=True) - os.makedirs('.ctx-saved') + shutil.rmtree(".ctx-saved", ignore_errors=True) + os.makedirs(".ctx-saved") # Running under MPI - #------------------ + # ------------------ if self.use_mpi: - # Synchronize in the beginning - assert self.size == len(self.modules) + 1,\ - 'Incorrect number of processes (Required %r, got %r)'%\ - (len(self.modules) + 1, self.size) + assert self.size == len(self.modules) + 1, ( + "Incorrect number of processes (Required %r, got %r)" + % (len(self.modules) + 1, self.size) + ) self.comm.Barrier() # Assign an mpi rank to all ports of a module using the module list index # If a port has rank assignment from a previous run; leave it alone for mod in self.modules: - rank = self.modules.index(mod)+1 + rank = self.modules.index(mod) + 1 for port in mod.ports: if port.rank is None: port.rank = rank @@ -249,22 +261,21 @@ def __run(self, save=False): # Parallel run module in MPI if self.rank != 0: - mod = self.modules[self.rank-1] - self.log.info('Launching Module {}'.format(mod)) + mod = self.modules[self.rank - 1] + self.log.info("Launching Module {}".format(mod)) mod.run_and_save() # Sync here at the end self.comm.Barrier() # Running under Python multiprocessing - #------------------------------------- + # ------------------------------------- else: - # Parallel run all modules in Python multiprocessing processes = list() for mod in self.modules: - self.log.info('Launching Module {}'.format(mod)) + self.log.info("Launching Module {}".format(mod)) proc = Process(target=mod.run_and_save) processes.append(proc) proc.start() @@ -274,29 +285,31 @@ def __run(self, save=False): proc.join() # Reload saved modules - #--------------------- + # --------------------- if self.use_mpi: # make double sure all processes are in sync here before reading files # from disk self.comm.Barrier() num_files = 0 - for file_name in os.listdir('.ctx-saved'): + for file_name in os.listdir(".ctx-saved"): + self.gv_edges = list() # re-initialize since ports were not pickled - self.gv_edges = list() # re-initialize since ports were not pickled - - if file_name.endswith('.pkl'): + if file_name.endswith(".pkl"): num_files += 1 - file_name = os.path.join('.ctx-saved', file_name) - with open(file_name, 'rb') as fin: + file_name = os.path.join(".ctx-saved", file_name) + with open(file_name, "rb") as fin: module = pickle.load(fin) # reintroduce logging - module.log = logging.getLogger('cortix') + module.log = logging.getLogger("cortix") self.modules[module.id] = module if num_files and num_files != len(self.modules): - self.log.warning('Network::run(): not all modules reloaded from disk.\ - # modules = %i; # files = %i'%(len(self.modules), num_files)) + self.log.warning( + "Network::run(): not all modules reloaded from disk.\ + # modules = %i; # files = %i" + % (len(self.modules), num_files) + ) if self.use_mpi: # Make double sure all are in sync here before going forward @@ -304,8 +317,16 @@ def __run(self, save=False): # that do not exist anymore self.comm.Barrier() - def draw(self, graph_attr=None, node_attr=None, engine='twopi', lr=False, - size=None, ports=False, node_shape='hexagon'): + def draw( + self, + graph_attr=None, + node_attr=None, + engine="twopi", + lr=False, + size=None, + ports=False, + node_shape="hexagon", + ): """Build a `graphviz` graph and draw the network saving it to a file. Parameters @@ -389,29 +410,41 @@ def draw(self, graph_attr=None, node_attr=None, engine='twopi', lr=False, """ # Delete existing graph files if any. - if os.path.isfile(self.name+'.gv'): - os.remove(self.name+'.gv') - if os.path.isfile(self.name+'.gv.png'): - os.remove(self.name+'.gv.png') + if os.path.isfile(self.name + ".gv"): + os.remove(self.name + ".gv") + if os.path.isfile(self.name + ".gv.png"): + os.remove(self.name + ".gv.png") # Import here to avoid broken dependency. Only this method needs graphviz. - if self.gv_info == 'undirectional': + if self.gv_info == "undirectional": from graphviz import Graph else: from graphviz import Digraph if graph_attr is None: - graph_attr = {'splines':'true', 'overlap':'scale', 'ranksep':'1.5'} + graph_attr = {"splines": "true", "overlap": "scale", "ranksep": "1.5"} if node_attr is None: - node_attr = {'shape':node_shape, 'style':'filled'} - - if self.gv_info == 'undirectional': - graph = Graph(name=self.name, comment='Network::graphviz', format='png', - graph_attr=graph_attr, node_attr=node_attr, engine=engine) + node_attr = {"shape": node_shape, "style": "filled"} + + if self.gv_info == "undirectional": + graph = Graph( + name=self.name, + comment="Network::graphviz", + format="png", + graph_attr=graph_attr, + node_attr=node_attr, + engine=engine, + ) else: - graph = Digraph(name=self.name, comment='Network::graphviz', format='png', - graph_attr=graph_attr, node_attr=node_attr, engine=engine) + graph = Digraph( + name=self.name, + comment="Network::graphviz", + format="png", + graph_attr=graph_attr, + node_attr=node_attr, + engine=engine, + ) if lr: graph.attr(rankdir="LR") @@ -419,7 +452,7 @@ def draw(self, graph_attr=None, node_attr=None, engine='twopi', lr=False, if size: graph.attr(size=size) - #graph.attr('node', shape=node_shape) + # graph.attr('node', shape=node_shape) for idx, mod in enumerate(self.modules): graph.node(str(idx), mod.name) diff --git a/cortix/node.py b/cortix/node.py new file mode 100644 index 00000000..1a96cbdc --- /dev/null +++ b/cortix/node.py @@ -0,0 +1,17 @@ +import networkx as nx +from .module import Module + + +class Graph: + def __init__(self): + self.g = nx.Digraph() + + def add(self, mod): + assert isinstance(mod, Module), "mod must be of type Module!" + self.g.add_node(mod) + mod.id = len(self.f.nodes) + + def connect(self, m1, m2): + assert isinstance(m1, Module), "m1 must be of type Module!" + assert isinstance(m2, Module), "m2 must be of type Module!" + self.g.add_edge(m1, m2) diff --git a/cortix/src/port.py b/cortix/port.py similarity index 92% rename from cortix/src/port.py rename to cortix/port.py index 98142f10..a5b70ee8 100644 --- a/cortix/src/port.py +++ b/cortix/port.py @@ -5,6 +5,7 @@ from multiprocessing import Pipe + class Port: """Provides a method of communication between modules. @@ -38,6 +39,7 @@ def __init__(self, name=None, use_mpi=False): if self.use_mpi: from mpi4py import MPI + self.comm = MPI.COMM_WORLD self.rank = None else: @@ -60,7 +62,7 @@ def connect(self, port): None """ - assert isinstance(port, Port), 'Connecting port must be of Port type' + assert isinstance(port, Port), "Connecting port must be of Port type" self.connected_port = port port.connected_port = self @@ -111,6 +113,7 @@ def __is_connected(self): return True else: return False + is_connected = property(__is_connected, None, None, None) def recv(self): @@ -128,8 +131,9 @@ def recv(self): if self.connected_port: if self.use_mpi: # This is an MPI blocking receive - return self.comm.recv(source=self.connected_port.rank, - tag=self.connected_port.id) + return self.comm.recv( + source=self.connected_port.rank, tag=self.connected_port.id + ) else: return self.pipe.recv() @@ -145,10 +149,11 @@ def __repr__(self): return self.name -if __name__ == '__main__': + +if __name__ == "__main__": # Create some ports - p1 = Port('test1') - p2 = Port('test2') + p1 = Port("test1") + p2 = Port("test2") # Connect the ports p1.connect(p2) diff --git a/cortix/src/node.py b/cortix/src/node.py deleted file mode 100644 index 53e3127a..00000000 --- a/cortix/src/node.py +++ /dev/null @@ -1,17 +0,0 @@ -import networkx as nx -from cortix.src.module import Module - -class Graph: - def __init__(self): - self.g = nx.Digraph() - - def add(self, mod): - assert(isinstance(mod, Module)), "mod must be of type Module!" - self.g.add_node(mod) - mod.id = len(self.f.nodes) - - def connect(self, m1, m2): - assert(isinstance(m1, Module)), "m1 must be of type Module!" - assert(isinstance(m2, Module)), "m2 must be of type Module!" - self.g.add_edge(m1, m2) - diff --git a/cortix/support/chemeng/reaction_mechanism.py b/cortix/support/chemeng/reaction_mechanism.py index 1e473dd3..ddb4ea8e 100644 --- a/cortix/support/chemeng/reaction_mechanism.py +++ b/cortix/support/chemeng/reaction_mechanism.py @@ -2,12 +2,11 @@ # -*- coding: utf-8 -*- # This file is part of the Cortix toolkit environment # https://cortix.org -"""Suupport class for working with chemical reactions. -""" +"""Suupport class for working with chemical reactions.""" import copy import math -import numpy # used in asserts +import numpy # used in asserts import numpy as np import scipy as sp from itertools import combinations @@ -16,6 +15,7 @@ from cortix.support.species import Species from cortix.support.units import Units as unit + class ReactionMechanism: """Chemical reaction mechanism. @@ -62,8 +62,15 @@ class ReactionMechanism: print(rxn_mech.latex_rxn) in a python script or Jupyter notebook. """ - def __init__(self, header='no-header', file_name=None, mechanism=None, order_species=True, - reparam=False, bounds=None): + def __init__( + self, + header="no-header", + file_name=None, + mechanism=None, + order_species=True, + reparam=False, + bounds=None, + ): """Module class constructor. Build data structures for a reaction mechanism. Namely, species list, @@ -141,7 +148,7 @@ def __init__(self, header='no-header', file_name=None, mechanism=None, order_spe Examples -------- - """ + """ assert file_name is not None or mechanism is not None assert isinstance(header, str) @@ -155,7 +162,7 @@ def __init__(self, header='no-header', file_name=None, mechanism=None, order_spe assert isinstance(file_name, str) assert mechanism is None - finput = open(file_name, 'rt') + finput = open(file_name, "rt") mechanism = list() @@ -169,24 +176,23 @@ def __init__(self, header='no-header', file_name=None, mechanism=None, order_spe self.reparam = reparam - try: - self.kf_bnds = bounds['kf'] + self.kf_bnds = bounds["kf"] except: self.kf_bnds = None try: - self.kb_bnds = bounds['kb'] + self.kb_bnds = bounds["kb"] except: self.kb_bnds = None try: - self.alpha_bnds = bounds['alpha'] + self.alpha_bnds = bounds["alpha"] except: self.alpha_bnds = None try: - self.beta_bnds = bounds['beta'] + self.beta_bnds = bounds["beta"] except: self.beta_bnds = None @@ -202,67 +208,74 @@ def __init__(self, header='no-header', file_name=None, mechanism=None, order_spe # First: read the data in the mechanism for m_i in mechanism: - - if m_i[0].strip() == '#': - if self.header == 'no-header': - self.header = m_i.strip() + '\n' + if m_i[0].strip() == "#": + if self.header == "no-header": + self.header = m_i.strip() + "\n" else: - self.header += m_i.strip() + '\n' + self.header += m_i.strip() + "\n" continue self.__original_mechanism.append(m_i) - assert m_i.find(':') == -1, 'Change all ":" to ";".' + assert m_i.find(":") == -1, 'Change all ":" to ";".' - data = m_i.split(';') + data = m_i.split(";") self.reactions.append(data[0].strip()) # do not save comments tmp_dict = dict() - if len(data) > 1: # if semi-colon separated data exists - + if len(data) > 1: # if semi-colon separated data exists for d in data[1:]: datum = d.strip() - name = datum.split('=')[0].strip() - val_str = datum.split('=')[1].strip() + name = datum.split("=")[0].strip() + val_str = datum.split("=")[1].strip() # alpha and beta names are reserved for the exponents of the reaction rate law # alpha and beta cases; convert tuple of integers into dictionary - if name in ('alpha', 'beta'): - assert '(' in val_str and ')' in val_str - alpha_or_beta = val_str[1:-1] # ignore "(" and ")" - alpha_or_beta_dict = {} # dict() + if name in ("alpha", "beta"): + assert "(" in val_str and ")" in val_str + alpha_or_beta = val_str[1:-1] # ignore "(" and ")" + alpha_or_beta_dict = {} # dict() i = 0 - for val_s in alpha_or_beta.split(','): + for val_s in alpha_or_beta.split(","): val = float(val_s.strip()) if self.reparam and val != 0.0: - #WARNING alpha_or_beta_dict[i] = math.log(val) - alpha_or_beta_dict[i] = val # user must pass the reparm theta values + # WARNING alpha_or_beta_dict[i] = math.log(val) + alpha_or_beta_dict[i] = ( + val # user must pass the reparm theta values + ) elif val == 0: - alpha_or_beta_dict[i] = -9999 # internal flag for inactive species + alpha_or_beta_dict[ + i + ] = -9999 # internal flag for inactive species else: alpha_or_beta_dict[i] = val i += 1 tmp_dict[name] = alpha_or_beta_dict # any other colon separated data - elif name == 'info': + elif name == "info": tmp_dict[name] = val_str - elif name == 'k_f' or name == 'k_b': + elif name == "k_f" or name == "k_b": if self.reparam: - #WARNING tmp_dict[name] = math.log(float(val_str)) - tmp_dict[name] = float(val_str) # user must pass the reparam theta values + # WARNING tmp_dict[name] = math.log(float(val_str)) + tmp_dict[name] = float( + val_str + ) # user must pass the reparam theta values else: tmp_dict[name] = float(val_str) else: tmp_dict[name] = float(val_str) # mass transfer relaxation reaction sanity check - if 'k_eq' in tmp_dict: - assert 'tau' in tmp_dict - if 'tau' in tmp_dict and 'k_eq' not in tmp_dict: - print('WARNING: user must provide a k_eq_func(spc_molar_cc, temperature=None) for %s'%(data[0])) + if "k_eq" in tmp_dict: + assert "tau" in tmp_dict + if "tau" in tmp_dict and "k_eq" not in tmp_dict: + print( + "WARNING: user must provide a k_eq_func(spc_molar_cc, temperature=None) for %s" + % (data[0]) + ) self.data.append(tmp_dict) @@ -271,46 +284,45 @@ def __init__(self, header='no-header', file_name=None, mechanism=None, order_spe species_tmp = list() # temporary list for species for idx, rxn in enumerate(self.reactions): - # the order of the following test matters; test reversible reaction first - tmp = rxn.split('<=>') + tmp = rxn.split("<=>") n_terms = len(tmp) assert n_terms == 1 or n_terms == 2 - if n_terms == 1: # if no previous split - tmp = rxn.split('<->') + if n_terms == 1: # if no previous split + tmp = rxn.split("<->") n_terms = len(tmp) assert n_terms == 1 or n_terms == 2 - if n_terms == 1: # if no previous split - tmp = rxn.split('->') + if n_terms == 1: # if no previous split + tmp = rxn.split("->") n_terms = len(tmp) assert n_terms == 1 or n_terms == 2 - if n_terms == 1: # if no previous split - tmp = rxn.split('<-') + if n_terms == 1: # if no previous split + tmp = rxn.split("<-") n_terms = len(tmp) assert n_terms == 1 or n_terms == 2 - assert n_terms == 2 , 'tmp = %r '%tmp # must have two terms + assert n_terms == 2, "tmp = %r " % tmp # must have two terms left = tmp[0].strip() right = tmp[1].strip() - left_terms = left.split(' + ') - right_terms = right.split(' + ') + left_terms = left.split(" + ") + right_terms = right.split(" + ") terms = [t.strip() for t in left_terms] + [t.strip() for t in right_terms] for i in terms: - tmp = i.split(' ') - assert len(tmp)==1 or len(tmp)==2, ' tmp = %r '%tmp + tmp = i.split(" ") + assert len(tmp) == 1 or len(tmp) == 2, " tmp = %r " % tmp if len(tmp) == 2: species_tmp.append(tmp[1].strip()) else: species_tmp.append(i.strip()) - species_filter = set(species_tmp) # filter species as a set + species_filter = set(species_tmp) # filter species as a set - self.species_names = list(species_filter) # convert species set to list + self.species_names = list(species_filter) # convert species set to list if order_species: self.species_names = sorted(self.species_names) @@ -329,91 +341,112 @@ def __init__(self, header='no-header', file_name=None, mechanism=None, order_spe self.reaction_direction_symbol = list() for r in self.reactions: + i_row = self.reactions.index( + r + ) # leave this here to catch repeated reactions! - i_row = self.reactions.index(r) # leave this here to catch repeated reactions! - - tmp = r.split(' -> ') + tmp = r.split(" -> ") n_terms = len(tmp) assert n_terms == 1 or n_terms == 2 # n_terms = 1 means no split if n_terms == 2: - self.reaction_direction_symbol.append('->') + self.reaction_direction_symbol.append("->") if n_terms == 1: - tmp = r.split(' <-> ') + tmp = r.split(" <-> ") n_terms = len(tmp) assert n_terms == 1 or n_terms == 2 # n_terms = 1 means no split if n_terms == 2: - self.reaction_direction_symbol.append('<->') + self.reaction_direction_symbol.append("<->") if n_terms == 1: - tmp = r.split(' <=> ') + tmp = r.split(" <=> ") n_terms = len(tmp) assert n_terms == 1 or n_terms == 2 # n_terms = 1 means no split if n_terms == 2: - self.reaction_direction_symbol.append('<=>') + self.reaction_direction_symbol.append("<=>") if n_terms == 1: - tmp = r.split(' <- ') + tmp = r.split(" <- ") n_terms = len(tmp) - assert n_terms == 1 or n_terms == 2 # n_terms = 1 means no split + assert ( + n_terms == 1 or n_terms == 2 + ) # n_terms = 1 means no split if n_terms == 2: - self.reaction_direction_symbol.append('<-') + self.reaction_direction_symbol.append("<-") assert n_terms == 2 left = tmp[0] - left_terms = left.split(' + ') + left_terms = left.split(" + ") left_terms = [t.strip() for t in left_terms] right = tmp[1] - right_terms = right.split(' + ') + right_terms = right.split(" + ") right_terms = [t.strip() for t in right_terms] for t in left_terms: - tmp = t.split(' ') + tmp = t.split(" ") if len(tmp) == 2: coeff = float(tmp[0].strip()) species_member = tmp[1].strip() j_col = self.species_names.index(species_member) - assert s_mtrx[i_row, j_col] == 0.0, \ - 'duplicates not allowed r%r: %r %r %r'%\ - (i_row, r, species_member, s_mtrx[i_row, j_col]) + assert s_mtrx[i_row, j_col] == 0.0, ( + "duplicates not allowed r%r: %r %r %r" + % (i_row, r, species_member, s_mtrx[i_row, j_col]) + ) s_mtrx[i_row, j_col] = -1.0 * coeff else: species_member = tmp[0].strip() j_col = self.species_names.index(species_member) - assert s_mtrx[i_row, j_col] == 0.0, \ - 'duplicates not allowed r%r: %r %r %r\n%r\n%r'%\ - (i_row, r, species_member, s_mtrx[i_row, j_col], s_mtrx[i_row,:], - self.species_names) + assert s_mtrx[i_row, j_col] == 0.0, ( + "duplicates not allowed r%r: %r %r %r\n%r\n%r" + % ( + i_row, + r, + species_member, + s_mtrx[i_row, j_col], + s_mtrx[i_row, :], + self.species_names, + ) + ) s_mtrx[i_row, j_col] = -1.0 - if 'alpha' in self.data[i_row].keys(): + if "alpha" in self.data[i_row].keys(): species_name = self.species_names[j_col] - assert len(self.data[i_row]['alpha']) == len(left_terms), 'Incorrect length of alpha.' + assert len(self.data[i_row]["alpha"]) == len( + left_terms + ), "Incorrect length of alpha." # replace species index with name - self.data[i_row]['alpha'][species_name] = self.data[i_row]['alpha'].pop(left_terms.index(t)) + self.data[i_row]["alpha"][species_name] = self.data[i_row][ + "alpha" + ].pop(left_terms.index(t)) for t in right_terms: - tmp = t.split(' ') + tmp = t.split(" ") if len(tmp) == 2: coeff = float(tmp[0].strip()) species_member = tmp[1].strip() j_col = self.species_names.index(species_member) - assert s_mtrx[i_row, j_col] == 0.0, \ - 'duplicates not allowed r%r: %r %r %r'%\ - (i_row, r, species_member, s_mtrx[i_row, j_col]) + assert s_mtrx[i_row, j_col] == 0.0, ( + "duplicates not allowed r%r: %r %r %r" + % (i_row, r, species_member, s_mtrx[i_row, j_col]) + ) s_mtrx[i_row, j_col] = 1.0 * coeff else: species_member = tmp[0].strip() j_col = self.species_names.index(species_member) - assert s_mtrx[i_row, j_col] == 0.0, \ - 'duplicates not allowed r%r: %r %r %r'%\ - (i_row, r, species_member, s_mtrx[i_row, j_col]) + assert s_mtrx[i_row, j_col] == 0.0, ( + "duplicates not allowed r%r: %r %r %r" + % (i_row, r, species_member, s_mtrx[i_row, j_col]) + ) s_mtrx[i_row, j_col] = 1.0 - if 'beta' in self.data[i_row].keys(): + if "beta" in self.data[i_row].keys(): species_name = self.species_names[j_col] - assert len(self.data[i_row]['beta']) == len(right_terms), 'Incorrect length of beta.' + assert len(self.data[i_row]["beta"]) == len( + right_terms + ), "Incorrect length of beta." # replace species index with name - self.data[i_row]['beta'][species_name] = self.data[i_row]['beta'].pop(right_terms.index(t)) + self.data[i_row]["beta"][species_name] = self.data[i_row][ + "beta" + ].pop(right_terms.index(t)) self.stoic_mtrx = s_mtrx @@ -422,18 +455,18 @@ def __init__(self, header='no-header', file_name=None, mechanism=None, order_spe # Fill-in missing k_f, k_b, alpha, and beta for idx, dat in enumerate(self.data): - if 'k_f' not in dat.keys(): + if "k_f" not in dat.keys(): if self.reparam: - dat['k_f'] = -1e+10 # math.log(0.0) + dat["k_f"] = -1e10 # math.log(0.0) else: - dat['k_f'] = 0.0 - if 'k_b' not in dat.keys(): + dat["k_f"] = 0.0 + if "k_b" not in dat.keys(): if self.reparam: - dat['k_b'] = -1e+10 # math.log(0.0) + dat["k_b"] = -1e10 # math.log(0.0) else: - dat['k_b'] = 0.0 - if 'alpha' not in dat.keys(): - (reactants_ids, ) = np.where(self.stoic_mtrx[idx, :] < 0) + dat["k_b"] = 0.0 + if "alpha" not in dat.keys(): + (reactants_ids,) = np.where(self.stoic_mtrx[idx, :] < 0) tmp = dict() for j in reactants_ids: spc_name = self.species_names[j] @@ -441,9 +474,9 @@ def __init__(self, header='no-header', file_name=None, mechanism=None, order_spe tmp[spc_name] = math.log(abs(self.stoic_mtrx[idx, j])) else: tmp[spc_name] = abs(self.stoic_mtrx[idx, j]) - dat['alpha'] = tmp - if 'beta' not in dat.keys(): - (products_ids, ) = np.where(self.stoic_mtrx[idx, :] > 0) + dat["alpha"] = tmp + if "beta" not in dat.keys(): + (products_ids,) = np.where(self.stoic_mtrx[idx, :] > 0) tmp = dict() for j in products_ids: spc_name = self.species_names[j] @@ -451,12 +484,12 @@ def __init__(self, header='no-header', file_name=None, mechanism=None, order_spe tmp[spc_name] = math.log(abs(self.stoic_mtrx[idx, j])) else: tmp[spc_name] = abs(self.stoic_mtrx[idx, j]) - dat['beta'] = tmp + dat["beta"] = tmp # Fill-in missing info for idx, dat in enumerate(self.data): - if 'info' not in dat.keys(): - dat['info'] = 'no-info' + if "info" not in dat.keys(): + dat["info"] = "no-info" def mass_balance_residuals(self): """Reaction mass balance residual vector. @@ -506,7 +539,7 @@ def is_mass_conserved(self, tol=1e-10): return True if residual < tol else False def rank_analysis(self, verbose=False, tol=1e-8): - '''Compute the rank of the stoichiometric matrix. + """Compute the rank of the stoichiometric matrix. This will establish rank deficiency. @@ -520,35 +553,42 @@ def rank_analysis(self, verbose=False, tol=1e-8): ------- rank: int - ''' + """ - #assert self.is_mass_conserved(tol), 'fatal: mass conservation failed' + # assert self.is_mass_conserved(tol), 'fatal: mass conservation failed' s_rank = np.linalg.matrix_rank(self.stoic_mtrx, tol=tol) assert s_rank <= min(self.stoic_mtrx.shape) if verbose: - print('# reactions = ', len(self.reactions)) - print('# species = ', len(self.species)) - print('rank of S = ', s_rank) + print("# reactions = ", len(self.reactions)) + print("# species = ", len(self.species)) + print("rank of S = ", s_rank) if s_rank == min(self.stoic_mtrx.shape): - print('S is full rank.') + print("S is full rank.") else: - print('S is rank deficient.') + print("S is rank deficient.") if s_rank == self.stoic_mtrx.shape[1]: - print('***********************') - print('Warning: rank = n') - print('Reaction mechanism fails mass conservation') - print('***********************') + print("***********************") + print("Warning: rank = n") + print("Reaction mechanism fails mass conservation") + print("***********************") return s_rank - def r_vec(self, spc_molar_cc_vec, temperature=273.15*unit.K, theta_kf_vec=None, theta_kb_vec=None, - theta_alpha_lst=None, theta_beta_lst=None): - '''Compute a reaction rate density vector. + def r_vec( + self, + spc_molar_cc_vec, + temperature=273.15 * unit.K, + theta_kf_vec=None, + theta_kb_vec=None, + theta_alpha_lst=None, + theta_beta_lst=None, + ): + """Compute a reaction rate density vector. This is the most common reaction rate expression for homogeneous reactions. @@ -595,8 +635,10 @@ def r_vec(self, spc_molar_cc_vec, temperature=273.15*unit.K, theta_kf_vec=None, List of beta power-law exponents as a matrix containing the product species ids. If not provided, it will be assembled internally from `self.data`. Must be repameterized (theta) if reparam is True. - ''' - assert isinstance(spc_molar_cc_vec, numpy.ndarray), 'type(spc_molar_cc_vec) = %r'%(type(spc_molar_cc_vec)) + """ + assert isinstance(spc_molar_cc_vec, numpy.ndarray), ( + "type(spc_molar_cc_vec) = %r" % (type(spc_molar_cc_vec)) + ) assert spc_molar_cc_vec.size == len(self.species) # Initialize kf_vec @@ -609,7 +651,9 @@ def r_vec(self, spc_molar_cc_vec, temperature=273.15*unit.K, theta_kf_vec=None, # Initialize kb_vec if theta_kb_vec is not None: - assert isinstance(theta_kb_vec, np.ndarray), 'type(theta_kb_vec)=%r'%(type(theta_kb_vec)) + assert isinstance(theta_kb_vec, np.ndarray), "type(theta_kb_vec)=%r" % ( + type(theta_kb_vec) + ) assert theta_kb_vec.size == len(self.reactions) kb_vec = self.perform_reparam(theta_kb_vec, self.kb_bnds) else: @@ -618,7 +662,10 @@ def r_vec(self, spc_molar_cc_vec, temperature=273.15*unit.K, theta_kf_vec=None, # Initialize alpha_lst if theta_alpha_lst is not None: assert isinstance(theta_alpha_lst, list) - assert len(theta_alpha_lst) == len(self.reactions), '# reactions=%r theta_alpha_lst=\n%r'%(len(self.reactions),theta_alpha_lst) + assert len(theta_alpha_lst) == len(self.reactions), ( + "# reactions=%r theta_alpha_lst=\n%r" + % (len(self.reactions), theta_alpha_lst) + ) assert isinstance(theta_alpha_lst[0], np.ndarray) assert theta_alpha_lst[0].shape[0] == 2 @@ -632,7 +679,9 @@ def r_vec(self, spc_molar_cc_vec, temperature=273.15*unit.K, theta_kf_vec=None, assert isinstance(theta_beta_lst, list) assert len(theta_beta_lst) == len(self.reactions) assert isinstance(theta_beta_lst[0], np.ndarray) - assert theta_beta_lst[0].shape[0] == 2 #WARNING some issue with backward reaction with one species + assert ( + theta_beta_lst[0].shape[0] == 2 + ) # WARNING some issue with backward reaction with one species theta_beta_lst = copy.deepcopy(theta_beta_lst) beta_lst = self.perform_reparam(theta_beta_lst, self.beta_bnds) @@ -642,63 +691,69 @@ def r_vec(self, spc_molar_cc_vec, temperature=273.15*unit.K, theta_kf_vec=None, # Compute the reaction rate density vector r_vec = np.zeros(len(self.reactions), dtype=np.float64) - for (idx, rxn_data) in enumerate(self.data): - + for idx, rxn_data in enumerate(self.data): alpha_mtrx = alpha_lst[idx] reactants_ids = alpha_mtrx[0, :].astype(int) - reactants_molar_cc = spc_molar_cc_vec[reactants_ids] # must be ordered as in rxn_mech - - #reactants_molar_cc[reactants_molar_cc < 0] = 0.0 + reactants_molar_cc = spc_molar_cc_vec[ + reactants_ids + ] # must be ordered as in rxn_mech - r_vec[idx] = kf_vec[idx] * np.prod(reactants_molar_cc**alpha_mtrx[1, :]) + # reactants_molar_cc[reactants_molar_cc < 0] = 0.0 - for (idx, rxn_data) in enumerate(self.data): + r_vec[idx] = kf_vec[idx] * np.prod(reactants_molar_cc ** alpha_mtrx[1, :]) + for idx, rxn_data in enumerate(self.data): beta_mtrx = beta_lst[idx] products_ids = beta_mtrx[0, :].astype(int) - products_molar_cc = spc_molar_cc_vec[products_ids] # must be oredered as in rxn_mech + products_molar_cc = spc_molar_cc_vec[ + products_ids + ] # must be oredered as in rxn_mech - #products_molar_cc[products_molar_cc < 0] = 0.0 + # products_molar_cc[products_molar_cc < 0] = 0.0 - r_vec[idx] -= kb_vec[idx] * np.prod(products_molar_cc**beta_mtrx[1, :]) + r_vec[idx] -= kb_vec[idx] * np.prod(products_molar_cc ** beta_mtrx[1, :]) # If k_eq and tau are present, override reaction rate with mass transfer relaxation # Complexation case - for (idx, rxn_data) in enumerate(self.data): - if 'tau' in rxn_data: - tau = rxn_data['tau'] + for idx, rxn_data in enumerate(self.data): + if "tau" in rxn_data: + tau = rxn_data["tau"] - if 'k_eq' in rxn_data: - k_eq = rxn_data['k_eq'] + if "k_eq" in rxn_data: + k_eq = rxn_data["k_eq"] else: - k_eq_func = rxn_data['k_eq_func'] - k_eq = k_eq_func(temperature, self.species, spc_molar_cc_vec) + k_eq_func = rxn_data["k_eq_func"] + k_eq = k_eq_func(temperature, self.species, spc_molar_cc_vec) reactants_ids = alpha_mtrx[0, :].astype(int) - #assert len(reactants_ids) == 2 + # assert len(reactants_ids) == 2 if len(reactants_ids) != 2: continue - reactants_molar_cc = spc_molar_cc_vec[reactants_ids] # must be ordered as in rxn_mech + reactants_molar_cc = spc_molar_cc_vec[ + reactants_ids + ] # must be ordered as in rxn_mech products_ids = beta_mtrx[0, :].astype(int) - #assert len(products_ids) == 1 + # assert len(products_ids) == 1 if len(reactants_ids) != 1: continue - products_molar_cc = spc_molar_cc_vec[products_ids] # must be oredered as in rxn_mech + products_molar_cc = spc_molar_cc_vec[ + products_ids + ] # must be oredered as in rxn_mech complex_former_molar_cc = reactants_molar_cc[-1] - complex_molar_cc = products_molar_cc[0] - complex_molar_cc_eq = k_eq * complex_former_molar_cc + complex_molar_cc = products_molar_cc[0] + complex_molar_cc_eq = k_eq * complex_former_molar_cc - r_vec[idx] = - 1/tau * (complex_molar_cc - complex_molar_cc_eq) + r_vec[idx] = -1 / tau * (complex_molar_cc - complex_molar_cc_eq) # Phase partition case # don't remember this...working on it... - #for (idx, rxn_data) in enumerate(self.data): + # for (idx, rxn_data) in enumerate(self.data): # if 'tau' in rxn_data: # tau = rxn_data['tau'] # if 'k_eq' in rxn_data: @@ -709,37 +764,47 @@ def r_vec(self, spc_molar_cc_vec, temperature=273.15*unit.K, theta_kf_vec=None, return r_vec - def rxn_rate_law(self, spc_molar_cc_vec, kf_vec=None, kb_vec=None, alpha_lst=None, beta_lst=None): - '''See r_vec. - ''' + def rxn_rate_law( + self, spc_molar_cc_vec, kf_vec=None, kb_vec=None, alpha_lst=None, beta_lst=None + ): + """See r_vec.""" return self.r_vec(spc_molar_cc_vec, kf_vec, kb_vec, alpha_lst, beta_lst) - def g_vec(self, spc_molar_cc_vec, temperature=273.15*unit.K, - theta_kf_vec=None, theta_kb_vec=None, - theta_alpha_lst=None, theta_beta_lst=None): - '''Compute the species production rate density vector. + def g_vec( + self, + spc_molar_cc_vec, + temperature=273.15 * unit.K, + theta_kf_vec=None, + theta_kb_vec=None, + theta_alpha_lst=None, + theta_beta_lst=None, + ): + """Compute the species production rate density vector. Compute g(c_vec, theta) with theta being a reparameterized value Parameters: ----------- - ''' + """ - g_vec = self.stoic_mtrx.transpose() @ self.r_vec(spc_molar_cc_vec, temperature, - theta_kf_vec, theta_kb_vec, - theta_alpha_lst, theta_beta_lst) + g_vec = self.stoic_mtrx.transpose() @ self.r_vec( + spc_molar_cc_vec, + temperature, + theta_kf_vec, + theta_kb_vec, + theta_alpha_lst, + theta_beta_lst, + ) return g_vec def __unbounded_reparam(self, lst_or_vec): - assert False lst_or_vec = copy.deepcopy(lst_or_vec) if isinstance(lst_or_vec, list): - beta_or_alpha_lst = lst_or_vec for idx, mtrx in enumerate(beta_or_alpha_lst): @@ -753,16 +818,15 @@ def __unbounded_reparam(self, lst_or_vec): return reparamed def __bounded_reparam(self, lst_or_vec, bnds): - '''Phi(theta) reparam with bounds on phi. + """Phi(theta) reparam with bounds on phi. That is, pass theta values (new parameters) through argument, return phi values (original parameters). - ''' + """ lst_or_vec = copy.deepcopy(lst_or_vec) # theta values if isinstance(lst_or_vec, list): - min_beta_or_alpha = bnds[0] max_beta_or_alpha = bnds[1] @@ -770,29 +834,29 @@ def __bounded_reparam(self, lst_or_vec, bnds): beta_or_alpha_lst = lst_or_vec # reusing space for irxn, mtrx in enumerate(theta_beta_or_alpha_lst): - a_vec = min_beta_or_alpha[irxn] b_vec = max_beta_or_alpha[irxn] theta_beta_or_alpha_vec = mtrx[1, :] - theta_beta_or_alpha_vec[theta_beta_or_alpha_vec>700] = 700 + theta_beta_or_alpha_vec[theta_beta_or_alpha_vec > 700] = 700 - beta_or_alpha_vec = b_vec - (b_vec - a_vec) / (1 + np.exp(theta_beta_or_alpha_vec)) + beta_or_alpha_vec = b_vec - (b_vec - a_vec) / ( + 1 + np.exp(theta_beta_or_alpha_vec) + ) beta_or_alpha_lst[irxn] = np.vstack([mtrx[0, :], beta_or_alpha_vec]) phi = beta_or_alpha_lst elif isinstance(lst_or_vec, np.ndarray): - theta_k_vec = lst_or_vec assert len(theta_k_vec.shape) == 1 min_k = bnds[0] max_k = bnds[1] - theta_k_vec[theta_k_vec>700] = 700 + theta_k_vec[theta_k_vec > 700] = 700 phi = max_k - (max_k - min_k) / (1 + np.exp(theta_k_vec)) @@ -802,7 +866,7 @@ def __bounded_reparam(self, lst_or_vec, bnds): return phi def perform_reparam(self, theta_lst_or_vec, bnds=None): - '''Phi(theta) function (reparameterization function). + """Phi(theta) function (reparameterization function). Phi is the original parameters (k_f, k_b, alpha, beta), theta is the nonlinear reparameterized values. @@ -810,8 +874,8 @@ def perform_reparam(self, theta_lst_or_vec, bnds=None): Reparam function phi(theta) = min_theta + (max_theta - min_theta) / (1 + np.exp(theta)) - ''' - #print(self.reparam) + """ + # print(self.reparam) if self.reparam == False: return theta_lst_or_vec @@ -820,14 +884,12 @@ def perform_reparam(self, theta_lst_or_vec, bnds=None): else: reparamed = self.__unbounded_reparam(theta_lst_or_vec) - return reparamed # return phi(theta) + return reparamed # return phi(theta) def __inv_unbounded_reparam(self, lst_or_vec): - lst_or_vec = copy.deepcopy(lst_or_vec) if isinstance(lst_or_vec, list): - beta_or_alpha_lst = lst_or_vec for idx, mtrx in enumerate(beta_or_alpha_lst): @@ -841,32 +903,34 @@ def __inv_unbounded_reparam(self, lst_or_vec): return reparamed def __inv_bounded_reparam(self, lst_or_vec, bnds): - '''Theta(phi) reparam with bounds on phi. + """Theta(phi) reparam with bounds on phi. That is, pass phi values (orig parameters) through argument, return theta values (new parameters). - ''' + """ - lst_or_vec = copy.deepcopy(lst_or_vec) # phi values + lst_or_vec = copy.deepcopy(lst_or_vec) # phi values if isinstance(lst_or_vec, list): min_beta_or_alpha = bnds[0] max_beta_or_alpha = bnds[1] beta_or_alpha_lst = lst_or_vec - theta_beta_or_alpha_lst = lst_or_vec # reusing space + theta_beta_or_alpha_lst = lst_or_vec # reusing space for irxn, mtrx in enumerate(beta_or_alpha_lst): - a_vec = min_beta_or_alpha[irxn] b_vec = max_beta_or_alpha[irxn] beta_or_alpha_vec = mtrx[1, :] - theta_beta_or_alpha_vec = np.log((a_vec - beta_or_alpha_vec) / \ - (beta_or_alpha_vec - b_vec)) + theta_beta_or_alpha_vec = np.log( + (a_vec - beta_or_alpha_vec) / (beta_or_alpha_vec - b_vec) + ) - theta_beta_or_alpha_lst[irxn] = np.vstack([mtrx[0, :], theta_beta_or_alpha_vec]) + theta_beta_or_alpha_lst[irxn] = np.vstack( + [mtrx[0, :], theta_beta_or_alpha_vec] + ) theta = theta_beta_or_alpha_lst @@ -875,12 +939,12 @@ def __inv_bounded_reparam(self, lst_or_vec, bnds): min_k = bnds[0] max_k = bnds[1] - theta = np.log((min_k - k_vec)/(k_vec - max_k)) + theta = np.log((min_k - k_vec) / (k_vec - max_k)) return theta def inv_reparam(self, phi_lst_or_vec, bnds=None): - '''Theta(phi) function (inverse parameterization function). + """Theta(phi) function (inverse parameterization function). Phi is the original parameters (k_f,k_b,alpha,beta), theta is the nonlinear reparameterized values. @@ -888,19 +952,19 @@ def inv_reparam(self, phi_lst_or_vec, bnds=None): Inverse of reparam function theta(phi) = ln( min_phi - phi / phi - max_phi ) for min_phi < phi < max_phi - ''' + """ if bnds is not None: params = self.__inv_bounded_reparam(phi_lst_or_vec, bnds) else: params = self.__inv_unbounded_reparam(phi_lst_or_vec) - return params # return theta(phi) + return params # return theta(phi) def __dphi_dtheta(self, theta_lst_or_vec, bnds=None): - '''Derivative of the original parameter (phi) wrt the working parameter (theta). + """Derivative of the original parameter (phi) wrt the working parameter (theta). That is, derivative of k_f, k_b, alpha, and beta wrt the nonlinear parameter theta. - ''' + """ theta_lst_or_vec = copy.deepcopy(theta_lst_or_vec) # TODO: move this to constructor for a single test @@ -919,146 +983,167 @@ def __dphi_dtheta(self, theta_lst_or_vec, bnds=None): lst_or_vec = theta_lst_or_vec if self.reparam is False: - if isinstance(lst_or_vec, list): - dphi_dtheta_lst = lst_or_vec # reuse space for irxn, theta_mtrx in enumerate(dphi_dtheta_lst): - dphi_dtheta_vec = np.ones(theta_mtrx[1, :].size) - dphi_dtheta_lst[irxn] = np.vstack([theta_mtrx[0, :], dphi_dtheta_vec]) + dphi_dtheta_lst[irxn] = np.vstack( + [theta_mtrx[0, :], dphi_dtheta_vec] + ) dphi_dtheta = dphi_dtheta_lst else: - dphi_dtheta_vec = np.ones(lst_or_vec.size) dphi_dtheta = dphi_dtheta_vec elif bnds is not None: - if isinstance(lst_or_vec, list): - min_beta_or_alpha = bnds[0] max_beta_or_alpha = bnds[1] dphi_dtheta_lst = lst_or_vec # reuse space for irxn, theta_mtrx in enumerate(lst_or_vec): - a_vec = min_beta_or_alpha[irxn] b_vec = max_beta_or_alpha[irxn] theta_vec = theta_mtrx[1, :] - dphi_dtheta_vec = ((b_vec - a_vec)*np.exp(theta_vec))/(np.exp(theta_vec) + 1)**2 + dphi_dtheta_vec = ((b_vec - a_vec) * np.exp(theta_vec)) / ( + np.exp(theta_vec) + 1 + ) ** 2 - dphi_dtheta_lst[irxn] = np.vstack([theta_mtrx[0, :], dphi_dtheta_vec]) + dphi_dtheta_lst[irxn] = np.vstack( + [theta_mtrx[0, :], dphi_dtheta_vec] + ) dphi_dtheta = dphi_dtheta_lst else: - theta_vec = lst_or_vec a_vec = bnds[0] b_vec = bnds[1] - #theta_vec[np.abs(theta_vec)<=1e-50] = 1e+50 + # theta_vec[np.abs(theta_vec)<=1e-50] = 1e+50 - dphi_dtheta_vec = ((b_vec - a_vec)*np.exp(theta_vec))/(np.exp(theta_vec) + 1)**2 + dphi_dtheta_vec = ((b_vec - a_vec) * np.exp(theta_vec)) / ( + np.exp(theta_vec) + 1 + ) ** 2 dphi_dtheta = dphi_dtheta_vec else: - - assert False # fix this + assert False # fix this dphi_dtheta = self.perform_reparam(lst_or_vec) return dphi_dtheta def __d2phi_dtheta2(self, theta_lst_or_vec, bnds=None): - '''2nd derivative of the original parameter (phi) wrt the working parameter (theta). - ''' + """2nd derivative of the original parameter (phi) wrt the working parameter (theta).""" theta_lst_or_vec = copy.deepcopy(theta_lst_or_vec) if self.reparam is False: - if isinstance(theta_lst_or_vec, list): - - d2phi_dtheta2_lst = theta_lst_or_vec # reuse data type + d2phi_dtheta2_lst = theta_lst_or_vec # reuse data type for idx, theta_mtrx in enumerate(d2phi_dtheta2_lst): - d2phi_dtheta2_vec = np.zeros(theta_mtrx[1, :].size) - d2phi_dtheta2_lst[idx] = np.vstack([theta_mtrx[0, :], d2phi_dtheta2_vec]) + d2phi_dtheta2_lst[idx] = np.vstack( + [theta_mtrx[0, :], d2phi_dtheta2_vec] + ) d2phi_dtheta2 = d2phi_dtheta2_lst else: - d2phi_dtheta2_vec = np.zeros(theta_lst_or_vec.size) d2phi_dtheta2 = d2phi_dtheta2_vec elif bnds is not None: if isinstance(theta_lst_or_vec, list): + min_beta_or_alpha = bnds[0] + max_beta_or_alpha = bnds[1] - min_beta_or_alpha = bnds[0] - max_beta_or_alpha = bnds[1] - - d2phi_dtheta2_lst = theta_lst_or_vec - - for idx, theta_mtrx in enumerate(d2phi_dtheta2_lst): + d2phi_dtheta2_lst = theta_lst_or_vec - a_vec = min_beta_or_alpha[idx] - b_vec = max_beta_or_alpha[idx] + for idx, theta_mtrx in enumerate(d2phi_dtheta2_lst): + a_vec = min_beta_or_alpha[idx] + b_vec = max_beta_or_alpha[idx] - theta_vec = theta_mtrx[1, :] + theta_vec = theta_mtrx[1, :] - d2phi_dtheta2_vec = (b_vec - a_vec) * (1 - np.exp(theta_vec)) * np.exp(theta_vec) / \ - (np.exp(theta_vec) + 1)**3 + d2phi_dtheta2_vec = ( + (b_vec - a_vec) + * (1 - np.exp(theta_vec)) + * np.exp(theta_vec) + / (np.exp(theta_vec) + 1) ** 3 + ) - #print('a_vec = ', a_vec) - #print('b_vec = ', b_vec) + # print('a_vec = ', a_vec) + # print('b_vec = ', b_vec) - #print('d2phi_dtheta2_vec = ', d2phi_dtheta2_vec) + # print('d2phi_dtheta2_vec = ', d2phi_dtheta2_vec) - d2phi_dtheta2_lst[idx] = np.vstack([theta_mtrx[0, :], d2phi_dtheta2_vec]) + d2phi_dtheta2_lst[idx] = np.vstack( + [theta_mtrx[0, :], d2phi_dtheta2_vec] + ) - d2phi_dtheta2 = d2phi_dtheta2_lst + d2phi_dtheta2 = d2phi_dtheta2_lst else: - theta_vec = theta_lst_or_vec a_vec = bnds[0] b_vec = bnds[1] - #print('a_vec = ', a_vec) - #print('b_vec = ', b_vec) + # print('a_vec = ', a_vec) + # print('b_vec = ', b_vec) - d2phi_dtheta2_vec = (b_vec - a_vec) * (1 - np.exp(theta_vec)) * np.exp(theta_vec) / \ - (np.exp(theta_vec) + 1)**3 + d2phi_dtheta2_vec = ( + (b_vec - a_vec) + * (1 - np.exp(theta_vec)) + * np.exp(theta_vec) + / (np.exp(theta_vec) + 1) ** 3 + ) - #print('d2phi_dtheta2_vec = ', d2phi_dtheta2_vec[0]) + # print('d2phi_dtheta2_vec = ', d2phi_dtheta2_vec[0]) d2phi_dtheta2 = d2phi_dtheta2_vec else: - d2phi_dtheta2 = self.perform_reparam(theta_lst_or_vec) return d2phi_dtheta2 - def species_prod_rate_dens(self, spc_molar_cc_vec, theta_kf_vec=None, theta_kb_vec=None, theta_alpha_lst=None, theta_beta_lst=None): - '''Compute the species production rate density vector. + def species_prod_rate_dens( + self, + spc_molar_cc_vec, + theta_kf_vec=None, + theta_kb_vec=None, + theta_alpha_lst=None, + theta_beta_lst=None, + ): + """Compute the species production rate density vector. Parameters: ----------- - ''' - - return self.g_vec(spc_molar_cc_vec, theta_kf_vec, theta_kb_vec, theta_alpha_lst, theta_beta_lst) + """ - def dr_dtheta_mtrx(self, spc_molar_cc_vec, - theta_kf_vec=None, theta_kb_vec=None, theta_alpha_lst=None, theta_beta_lst=None): - '''Partial derivative of the reaction rate law vector wrt working parameters. + return self.g_vec( + spc_molar_cc_vec, + theta_kf_vec, + theta_kb_vec, + theta_alpha_lst, + theta_beta_lst, + ) + + def dr_dtheta_mtrx( + self, + spc_molar_cc_vec, + theta_kf_vec=None, + theta_kb_vec=None, + theta_alpha_lst=None, + theta_beta_lst=None, + ): + """Partial derivative of the reaction rate law vector wrt working parameters. Theta is the working parameter (theta(phi)). @@ -1080,17 +1165,18 @@ def dr_dtheta_mtrx(self, spc_molar_cc_vec, This partial derivative matrix is instrumental to compute other quantities in particular the partial derivative of g wrt parameters, dg_dtheta. This is equal to the Jacobian matrix of the least squares residual. - ''' + """ assert isinstance(spc_molar_cc_vec, np.ndarray) assert spc_molar_cc_vec.size == len(self.species) - assert np.all(spc_molar_cc_vec >= 0), 'spc_molar_cc_vec =\n%r' % spc_molar_cc_vec + assert np.all(spc_molar_cc_vec >= 0), ( + "spc_molar_cc_vec =\n%r" % spc_molar_cc_vec + ) # -------------------------------------------- # partial_theta_kf(r) = P partial_theta_kf(kf) # -------------------------------------------- if theta_kf_vec is not None: - # Compute P if theta_alpha_lst is None: @@ -1099,10 +1185,9 @@ def dr_dtheta_mtrx(self, spc_molar_cc_vec, theta_alpha_lst = copy.deepcopy(theta_alpha_lst) alpha_lst = self.perform_reparam(theta_alpha_lst, self.alpha_bnds) - p_vec = np.zeros(len(self.reactions), dtype = np.float64) - - for (irxn, alpha_data_mtrx) in enumerate(alpha_lst): + p_vec = np.zeros(len(self.reactions), dtype=np.float64) + for irxn, alpha_data_mtrx in enumerate(alpha_lst): active_spc_ids = alpha_data_mtrx[0, :].astype(int) alpha_i_vec = alpha_data_mtrx[1, :] @@ -1134,7 +1219,6 @@ def dr_dtheta_mtrx(self, spc_molar_cc_vec, # partial_theta_kb(r) = - Q partial_theta_kb(kb) # -------------------------------------------- if theta_kb_vec is not None: - # Compute Q if theta_beta_lst is None: @@ -1143,10 +1227,9 @@ def dr_dtheta_mtrx(self, spc_molar_cc_vec, theta_beta_lst = copy.deepcopy(theta_beta_lst) beta_lst = self.perform_reparam(theta_beta_lst, self.beta_bnds) - q_vec = np.zeros(len(self.reactions), dtype = np.float64) - - for (irxn, beta_data_mtrx) in enumerate(beta_lst): + q_vec = np.zeros(len(self.reactions), dtype=np.float64) + for irxn, beta_data_mtrx in enumerate(beta_lst): active_spc_ids = beta_data_mtrx[0, :].astype(int) beta_i_vec = beta_data_mtrx[1, :] @@ -1178,7 +1261,6 @@ def dr_dtheta_mtrx(self, spc_molar_cc_vec, # partial_theta_alpha(r) = X partial_theta_alpha(alpha) # ----------------------------------------------------- if theta_alpha_lst is not None: - # Compute X = partial_alpha(r) # get kf @@ -1193,8 +1275,7 @@ def dr_dtheta_mtrx(self, spc_molar_cc_vec, alpha_lst = self.perform_reparam(theta_alpha_lst, self.alpha_bnds) - for (irxn, alpha_data_mtrx) in enumerate(alpha_lst): - + for irxn, alpha_data_mtrx in enumerate(alpha_lst): active_spc_ids = alpha_data_mtrx[0, :].astype(int) alpha_i_vec = alpha_data_mtrx[1, :] @@ -1207,14 +1288,18 @@ def dr_dtheta_mtrx(self, spc_molar_cc_vec, min_c_j = active_spc_molar_cc.min() if min_c_j <= 1e-25: - (jdx, ) = np.where(active_spc_molar_cc == min_c_j) - active_spc_molar_cc[jdx] = 1.0 # any non-zero value will do since rf_i will be zero + (jdx,) = np.where(active_spc_molar_cc == min_c_j) + active_spc_molar_cc[jdx] = ( + 1.0 # any non-zero value will do since rf_i will be zero + ) w_alpha_i_vec = np.log(active_spc_molar_cc) x_vec_i = rf_i * w_alpha_i_vec n_alpha_i = alpha_data_mtrx.shape[1] - x_mtrx_j_block = np.zeros((len(self.reactions), n_alpha_i), dtype = np.float64) + x_mtrx_j_block = np.zeros( + (len(self.reactions), n_alpha_i), dtype=np.float64 + ) x_mtrx_j_block[irxn, :] = x_vec_i @@ -1227,26 +1312,31 @@ def dr_dtheta_mtrx(self, spc_molar_cc_vec, for alpha_data_mtrx in alpha_lst: n_alphas += alpha_data_mtrx.shape[1] - assert x_mtrx.shape == (len(alpha_lst), n_alphas), 'n_alphas = %r; U shape = %r' % (n_alphas, x_mtrx.shape) + assert x_mtrx.shape == (len(alpha_lst), n_alphas), ( + "n_alphas = %r; U shape = %r" % (n_alphas, x_mtrx.shape) + ) # Compute partial_theta_alpha(alpha) dalpha_dtheta_lst = self.__dphi_dtheta(theta_alpha_lst, self.alpha_bnds) for dalpha_dtheta_data_mtrx in dalpha_dtheta_lst: - - #assert(dalpha_dtheta_data_mtrx[0,:] == alpha_data_mtrx[0,:]) # reactant IDs must match - dalpha_dtheta_mtrx_i = np.diag(dalpha_dtheta_data_mtrx[1,:]) + # assert(dalpha_dtheta_data_mtrx[0,:] == alpha_data_mtrx[0,:]) # reactant IDs must match + dalpha_dtheta_mtrx_i = np.diag(dalpha_dtheta_data_mtrx[1, :]) try: - dalpha_dtheta_mtrx = sp.linalg.block_diag(dalpha_dtheta_mtrx, dalpha_dtheta_mtrx_i) + dalpha_dtheta_mtrx = sp.linalg.block_diag( + dalpha_dtheta_mtrx, dalpha_dtheta_mtrx_i + ) except NameError: dalpha_dtheta_mtrx = sp.linalg.block_diag(dalpha_dtheta_mtrx_i) # Store product try: - dr_dtheta_mtrx = np.hstack([dr_dtheta_mtrx, x_mtrx @ dalpha_dtheta_mtrx]) + dr_dtheta_mtrx = np.hstack( + [dr_dtheta_mtrx, x_mtrx @ dalpha_dtheta_mtrx] + ) except NameError: dr_dtheta_mtrx = np.hstack([x_mtrx @ dalpha_dtheta_mtrx]) @@ -1254,7 +1344,6 @@ def dr_dtheta_mtrx(self, spc_molar_cc_vec, # partial_theta_beta(r) = Y partial_theta_beta(beta) # -------------------------------------------------- if theta_beta_lst is not None: - # Compute Y = partial_beta(r) # get kb @@ -1269,8 +1358,7 @@ def dr_dtheta_mtrx(self, spc_molar_cc_vec, beta_lst = self.perform_reparam(theta_beta_lst, self.beta_bnds) - for (irxn, beta_data_mtrx) in enumerate(beta_lst): - + for irxn, beta_data_mtrx in enumerate(beta_lst): active_spc_ids = beta_data_mtrx[0, :].astype(int) beta_i_vec = beta_data_mtrx[1, :] @@ -1283,14 +1371,18 @@ def dr_dtheta_mtrx(self, spc_molar_cc_vec, min_c_j = active_spc_molar_cc.min() if min_c_j <= 1e-25: - (jdx, ) = np.where(active_spc_molar_cc == min_c_j) - active_spc_molar_cc[jdx] = 1.0 # any non-zero value will do it since rb_i will be zero + (jdx,) = np.where(active_spc_molar_cc == min_c_j) + active_spc_molar_cc[jdx] = ( + 1.0 # any non-zero value will do it since rb_i will be zero + ) w_beta_i_vec = np.log(active_spc_molar_cc) y_vec_i = rb_i * w_beta_i_vec n_beta_i = beta_data_mtrx.shape[1] - y_mtrx_j_block = np.zeros((len(self.reactions), n_beta_i), dtype = np.float64) + y_mtrx_j_block = np.zeros( + (len(self.reactions), n_beta_i), dtype=np.float64 + ) y_mtrx_j_block[irxn, :] = y_vec_i @@ -1299,39 +1391,50 @@ def dr_dtheta_mtrx(self, spc_molar_cc_vec, except NameError: y_mtrx = np.hstack([y_mtrx_j_block]) - n_betas=0 + n_betas = 0 for beta_data_mtrx in beta_lst: n_betas += beta_data_mtrx.shape[1] - assert y_mtrx.shape == (len(beta_lst), n_betas), 'n_betas = %r; V shape = %r' % (n_betas, y_mtrx.shape) + assert y_mtrx.shape == (len(beta_lst), n_betas), ( + "n_betas = %r; V shape = %r" % (n_betas, y_mtrx.shape) + ) # Compute partial_theta_beta(beta) dbeta_dtheta_lst = self.__dphi_dtheta(theta_beta_lst, self.beta_bnds) for dbeta_dtheta_data_mtrx in dbeta_dtheta_lst: - - #assert(dbeta_dtheta_data_mtrx[0,:] == beta_data_mtrx[0,:]) # reactant IDs must match - dbeta_dtheta_mtrx_i = np.diag(dbeta_dtheta_data_mtrx[1,:]) + # assert(dbeta_dtheta_data_mtrx[0,:] == beta_data_mtrx[0,:]) # reactant IDs must match + dbeta_dtheta_mtrx_i = np.diag(dbeta_dtheta_data_mtrx[1, :]) try: - dbeta_dtheta_mtrx = sp.linalg.block_diag(dbeta_dtheta_mtrx, dbeta_dtheta_mtrx_i) + dbeta_dtheta_mtrx = sp.linalg.block_diag( + dbeta_dtheta_mtrx, dbeta_dtheta_mtrx_i + ) except NameError: dbeta_dtheta_mtrx = sp.linalg.block_diag(dbeta_dtheta_mtrx_i) # Store product try: - dr_dtheta_mtrx = np.hstack([dr_dtheta_mtrx, (-y_mtrx @ dbeta_dtheta_mtrx)]) + dr_dtheta_mtrx = np.hstack( + [dr_dtheta_mtrx, (-y_mtrx @ dbeta_dtheta_mtrx)] + ) except NameError: dr_dtheta_mtrx = np.hstack([-y_mtrx @ dbeta_dtheta_mtrx]) return dr_dtheta_mtrx - def dr_dtheta_mtrx_numerical(self, spc_molar_cc_vec, - kf_vec = None, kb_vec =None, alpha_lst=None, beta_lst=None, - h_small = 1e-6): - '''Numerical partial derivative of the reaction rate law vector wrt parameters. + def dr_dtheta_mtrx_numerical( + self, + spc_molar_cc_vec, + kf_vec=None, + kb_vec=None, + alpha_lst=None, + beta_lst=None, + h_small=1e-6, + ): + """Numerical partial derivative of the reaction rate law vector wrt parameters. The parameters in the derivative are ordered as: k_fs, k_bs, alphas, betas. If a parameter is `None`, it is not considered a varying parameter. @@ -1342,130 +1445,158 @@ def dr_dtheta_mtrx_numerical(self, spc_molar_cc_vec, That is, p = 2 * m + n_Ri + n_Pi, where n_Ri is the number of active reactant species, and n_Pi is the number of active product species. If say, alpha_lst is not a varying parameter, then n_Ri = 0. - ''' + """ assert isinstance(spc_molar_cc_vec, np.ndarray) assert spc_molar_cc_vec.size == len(self.species) - assert np.all(spc_molar_cc_vec >= 0), 'spc_molar_cc_vec =\n%r' % spc_molar_cc_vec + assert np.all(spc_molar_cc_vec >= 0), ( + "spc_molar_cc_vec =\n%r" % spc_molar_cc_vec + ) # -------------------------------- # Partial r_vec partial kf matrix # -------------------------------- if kf_vec is not None: - if alpha_lst is None: - (alpha_lst_local, _)=self.__get_power_law_exponents() + (alpha_lst_local, _) = self.__get_power_law_exponents() else: - alpha_lst_local=alpha_lst + alpha_lst_local = alpha_lst - dr_dk_f = np.zeros((len(self.reactions), len(self.reactions)), dtype = np.float64) - i_mtrx= np.eye(len(self.reactions), dtype = np.float64) + dr_dk_f = np.zeros( + (len(self.reactions), len(self.reactions)), dtype=np.float64 + ) + i_mtrx = np.eye(len(self.reactions), dtype=np.float64) for jdx in range(len(self.data)): + r_vec_h = self.r_vec( + spc_molar_cc_vec, + kf_vec=kf_vec + h_small * i_mtrx[:, jdx], + alpha_lst=alpha_lst_local, + ) + r_vec = self.r_vec( + spc_molar_cc_vec, kf_vec=kf_vec, alpha_lst=alpha_lst_local + ) - r_vec_h = self.r_vec(spc_molar_cc_vec, kf_vec = kf_vec + h_small*i_mtrx[:,jdx], alpha_lst =alpha_lst_local) - r_vec = self.r_vec(spc_molar_cc_vec, kf_vec = kf_vec, alpha_lst =alpha_lst_local) - - dr_dk_f[:, jdx]=(r_vec_h - r_vec) / h_small + dr_dk_f[:, jdx] = (r_vec_h - r_vec) / h_small try: - dr_dtheta_mtrx=np.hstack([dr_dtheta_mtrx, dr_dk_f]) + dr_dtheta_mtrx = np.hstack([dr_dtheta_mtrx, dr_dk_f]) except NameError: - dr_dtheta_mtrx=np.hstack([dr_dk_f]) + dr_dtheta_mtrx = np.hstack([dr_dk_f]) # -------------------------------- # Partial r_vec partial kb matrix # -------------------------------- if kb_vec is not None: - if beta_lst is None: - (_, beta_lst_local)=self.__get_power_law_exponents() + (_, beta_lst_local) = self.__get_power_law_exponents() else: - beta_lst_local=beta_lst + beta_lst_local = beta_lst - dr_dk_b = np.zeros((len(self.reactions), len(self.reactions)), dtype = np.float64) - i_mtrx= np.eye(len(self.reactions), dtype = np.float64) + dr_dk_b = np.zeros( + (len(self.reactions), len(self.reactions)), dtype=np.float64 + ) + i_mtrx = np.eye(len(self.reactions), dtype=np.float64) for jdx in range(len(self.data)): + r_vec_h = self.r_vec( + spc_molar_cc_vec, + kb_vec=kb_vec + h_small * i_mtrx[:, jdx], + beta_lst=beta_lst_local, + ) + r_vec = self.r_vec( + spc_molar_cc_vec, kb_vec=kb_vec, beta_lst=beta_lst_local + ) - r_vec_h = self.r_vec(spc_molar_cc_vec, kb_vec = kb_vec + h_small*i_mtrx[:,jdx], beta_lst =beta_lst_local) - r_vec = self.r_vec(spc_molar_cc_vec, kb_vec = kb_vec, beta_lst =beta_lst_local) - - dr_dk_b[:, jdx]=(r_vec_h - r_vec) / h_small + dr_dk_b[:, jdx] = (r_vec_h - r_vec) / h_small try: - dr_dtheta_mtrx=np.hstack([dr_dtheta_mtrx, dr_dk_b]) + dr_dtheta_mtrx = np.hstack([dr_dtheta_mtrx, dr_dk_b]) except NameError: - dr_dtheta_mtrx=np.hstack([dr_dk_b]) + dr_dtheta_mtrx = np.hstack([dr_dk_b]) # ----------------------------------- # Partial r_vec partial alpha matrix # ----------------------------------- if alpha_lst is not None: - - n_alphas=0 + n_alphas = 0 for alpha_mtrx in alpha_lst: n_alphas += alpha_mtrx.shape[1] - dr_dalpha= np.zeros((len(self.reactions), n_alphas), dtype = np.float64) - i_mtrx= np.eye(n_alphas, dtype = np.float64) + dr_dalpha = np.zeros((len(self.reactions), n_alphas), dtype=np.float64) + i_mtrx = np.eye(n_alphas, dtype=np.float64) if kf_vec is None: - (kf_vec_local, _)=self.__get_ks() + (kf_vec_local, _) = self.__get_ks() else: - kf_vec_local=kf_vec + kf_vec_local = kf_vec for jdx in range(n_alphas): + assert False, "FIXME" - assert False, 'FIXME' - - r_vec_h = self.r_vec(spc_molar_cc_vec, alpha_lst = alpha_lst + h_small*i_mtrx[:,jdx], kf_vec =kf_vec_local) - r_vec = self.r_vec(spc_molar_cc_vec, alpha_lst = alpha_lst, kf_vec =kf_vec_local) + r_vec_h = self.r_vec( + spc_molar_cc_vec, + alpha_lst=alpha_lst + h_small * i_mtrx[:, jdx], + kf_vec=kf_vec_local, + ) + r_vec = self.r_vec( + spc_molar_cc_vec, alpha_lst=alpha_lst, kf_vec=kf_vec_local + ) - dr_dalpha[:, jdx]=(r_vec_h - r_vec) / h_small + dr_dalpha[:, jdx] = (r_vec_h - r_vec) / h_small try: - dr_dtheta_mtrx=np.hstack([dr_dtheta_mtrx, dr_dalpha]) + dr_dtheta_mtrx = np.hstack([dr_dtheta_mtrx, dr_dalpha]) except NameError: - dr_dtheta_mtrx=np.hstack([dr_dalpha]) + dr_dtheta_mtrx = np.hstack([dr_dalpha]) # ---------------------------------- # Partial r_vec partial beta matrix # ---------------------------------- if beta_lst is not None: - - n_betas=0 + n_betas = 0 for beta_vec in beta_lst: # assert np.all(beta_vec>=0) n_betas += beta_vec.size - dr_dbeta = np.zeros((len(self.reactions), n_betas), dtype = np.float64) - i_mtrx= np.eye(n_betas, dtype = np.float64) + dr_dbeta = np.zeros((len(self.reactions), n_betas), dtype=np.float64) + i_mtrx = np.eye(n_betas, dtype=np.float64) if kb_vec is None: - (_, kb_vec_local)=self.__get_ks() + (_, kb_vec_local) = self.__get_ks() else: - kb_vec_local=kb_vec + kb_vec_local = kb_vec for jdx in range(n_betas): + assert False, "FIXME" - assert False, 'FIXME' + r_vec_h = self.r_vec( + spc_molar_cc_vec, + beta_lst=beta_lst + h_small * i_mtrx[:, jdx], + kb_vec=kb_vec_local, + ) + r_vec = self.r_vec( + spc_molar_cc_vec, beta_lst=beta_lst, kb_vec=kb_vec_local + ) - r_vec_h = self.r_vec(spc_molar_cc_vec, beta_lst = beta_lst + h_small*i_mtrx[:,jdx], kb_vec =kb_vec_local) - r_vec = self.r_vec(spc_molar_cc_vec, beta_lst = beta_lst, kb_vec =kb_vec_local) - - dr_dbeta[:, jdx]=(r_vec_h - r_vec) / h_small + dr_dbeta[:, jdx] = (r_vec_h - r_vec) / h_small try: - dr_dtheta_mtrx=np.hstack([dr_dtheta_mtrx, dr_dbeta]) + dr_dtheta_mtrx = np.hstack([dr_dtheta_mtrx, dr_dbeta]) except NameError: - dr_dtheta_mtrx=np.hstack([dr_dbeta]) + dr_dtheta_mtrx = np.hstack([dr_dbeta]) return dr_dtheta_mtrx - def dg_dtheta_mtrx(self, spc_molar_cc_vec, - theta_kf_vec=None, theta_kb_vec=None, theta_alpha_lst=None, theta_beta_lst=None): - '''Compute the partial derivative of the reaction rate density vector wrt to operating parameters. + def dg_dtheta_mtrx( + self, + spc_molar_cc_vec, + theta_kf_vec=None, + theta_kb_vec=None, + theta_alpha_lst=None, + theta_beta_lst=None, + ): + """Compute the partial derivative of the reaction rate density vector wrt to operating parameters. Compute dg_dtheta with theta being the operating parameters. This quantity is typically the negative of the Jacobian matrix in the leasts-squares optimization @@ -1473,17 +1604,28 @@ def dg_dtheta_mtrx(self, spc_molar_cc_vec, Parameters: ----------- - ''' + """ - dg_dtheta_mtrx = self.stoic_mtrx.transpose() @ self.dr_dtheta_mtrx(spc_molar_cc_vec, - theta_kf_vec, theta_kb_vec, - theta_alpha_lst, theta_beta_lst) + dg_dtheta_mtrx = self.stoic_mtrx.transpose() @ self.dr_dtheta_mtrx( + spc_molar_cc_vec, + theta_kf_vec, + theta_kb_vec, + theta_alpha_lst, + theta_beta_lst, + ) return dg_dtheta_mtrx - def d2ri_theta2_mtrx(self, rxn_idx, spc_molar_cc_vec, - kf_vec = None, kb_vec =None, alpha_lst=None, beta_lst=None): - '''Second partial derivatives of the ith reaction rate law wrt parameters. + def d2ri_theta2_mtrx( + self, + rxn_idx, + spc_molar_cc_vec, + kf_vec=None, + kb_vec=None, + alpha_lst=None, + beta_lst=None, + ): + """Second partial derivatives of the ith reaction rate law wrt parameters. Only the forward case reaction case is implemented. @@ -1491,14 +1633,16 @@ def d2ri_theta2_mtrx(self, rxn_idx, spc_molar_cc_vec, The matrix is p x p. Where p is the total number of parameters. That, is p = 2 * m + n_Ri + n_Pi, where n_Ri is the number of active reactant species, and n_Pi is the number of active product species. - ''' + """ assert isinstance(rxn_idx, int) assert rxn_idx <= len(self.reactions) assert isinstance(spc_molar_cc_vec, np.ndarray) assert spc_molar_cc_vec.size == len(self.species) - assert np.all(spc_molar_cc_vec >= 0), 'spc_molar_cc_vec =\n%r' % spc_molar_cc_vec + assert np.all(spc_molar_cc_vec >= 0), ( + "spc_molar_cc_vec =\n%r" % spc_molar_cc_vec + ) # ******************************************************************************************* # 1st row block @@ -1507,98 +1651,101 @@ def d2ri_theta2_mtrx(self, rxn_idx, spc_molar_cc_vec, # partial_kf(partial_kf r_i) # --------------------------- if kf_vec is not None: - - d_kf_d_kf_ri_mtrx= np.zeros((len(self.reactions), len(self.reactions)), dtype = np.float64) + d_kf_d_kf_ri_mtrx = np.zeros( + (len(self.reactions), len(self.reactions)), dtype=np.float64 + ) theta_vec = copy.deepcopy(kf_vec) d2kf_dtheta2_vec = self.__d2phi_dtheta2(theta_vec, self.kf_bnds) if alpha_lst is None: - (alpha_lst_local, _)=self.__get_power_law_exponents() + (alpha_lst_local, _) = self.__get_power_law_exponents() else: - alpha_lst_local= copy.deepcopy(alpha_lst) + alpha_lst_local = copy.deepcopy(alpha_lst) alpha_lst_local = self.perform_reparam(alpha_lst_local, self.alpha_bnds) + alpha_mtrx = alpha_lst_local[rxn_idx] - alpha_mtrx=alpha_lst_local[rxn_idx] - - reactants_ids=alpha_mtrx[0, :].astype(int) + reactants_ids = alpha_mtrx[0, :].astype(int) - reactants_molar_cc=spc_molar_cc_vec[reactants_ids] + reactants_molar_cc = spc_molar_cc_vec[reactants_ids] - spc_cc_power_prod=np.prod(reactants_molar_cc**alpha_mtrx[1, :]) + spc_cc_power_prod = np.prod(reactants_molar_cc ** alpha_mtrx[1, :]) - d_kf_d_kf_ri_mtrx[rxn_idx,rxn_idx]= d2kf_dtheta2_vec[rxn_idx] * spc_cc_power_prod + d_kf_d_kf_ri_mtrx[rxn_idx, rxn_idx] = ( + d2kf_dtheta2_vec[rxn_idx] * spc_cc_power_prod + ) # --------------------------- # partial_kb(partial_kf r_i) # --------------------------- if kf_vec is not None and kb_vec is not None: - - d_kb_d_kf_ri_mtrx= np.zeros((len(self.reactions), len(self.reactions)), dtype = np.float64) + d_kb_d_kf_ri_mtrx = np.zeros( + (len(self.reactions), len(self.reactions)), dtype=np.float64 + ) # ------------------------------ # partial_alpha(partial_kf r_i) # ------------------------------ if kf_vec is not None and alpha_lst is not None: - - - n_alphas=0 + n_alphas = 0 for alpha_mtrx in alpha_lst: n_alphas += alpha_mtrx.shape[1] - d_alpha_d_kf_ri_mtrx= np.zeros((len(self.reactions), n_alphas), dtype = np.float64) + d_alpha_d_kf_ri_mtrx = np.zeros( + (len(self.reactions), n_alphas), dtype=np.float64 + ) - theta_lst = copy.deepcopy(alpha_lst) #alpha's + theta_lst = copy.deepcopy(alpha_lst) # alpha's dalpha_dtheta_lst = self.__dphi_dtheta(theta_lst, self.alpha_bnds) - theta_vec = copy.deepcopy(kf_vec) #kf's + theta_vec = copy.deepcopy(kf_vec) # kf's dkf_dtheta_vec = self.__dphi_dtheta(theta_vec, self.kf_bnds) - alpha_lst_local = copy.deepcopy(alpha_lst) alpha_lst_local = self.perform_reparam(alpha_lst_local, self.alpha_bnds) - #kf_vec_local = copy.deepcopy(kf_vec) - #kf_vec = self.reparam(kf_vec, self.kf_bnds) + # kf_vec_local = copy.deepcopy(kf_vec) + # kf_vec = self.reparam(kf_vec, self.kf_bnds) - jdx_start=0 + jdx_start = 0 for idx in range(rxn_idx): - - alpha_mtrx=alpha_lst_local[idx] + alpha_mtrx = alpha_lst_local[idx] jdx_start += alpha_mtrx.shape[1] - rxn_idx_alpha_mtrx = alpha_lst_local[rxn_idx] rxn_idx_dalpha_dtheta_mtrx = dalpha_dtheta_lst[rxn_idx] - #assert(rxn_idx_dalpha_dtheta_mtrx[0,:] == rxn_idx_alpha_mtrx[0,:]) # reactant IDs must match + # assert(rxn_idx_dalpha_dtheta_mtrx[0,:] == rxn_idx_alpha_mtrx[0,:]) # reactant IDs must match - reactants_ids=rxn_idx_alpha_mtrx[0, :].astype(int) + reactants_ids = rxn_idx_alpha_mtrx[0, :].astype(int) - reactants_molar_cc=spc_molar_cc_vec[reactants_ids] + reactants_molar_cc = spc_molar_cc_vec[reactants_ids] - spc_cc_power_prod=np.prod(reactants_molar_cc**rxn_idx_alpha_mtrx[1, :]) + spc_cc_power_prod = np.prod(reactants_molar_cc ** rxn_idx_alpha_mtrx[1, :]) - min_c_j=reactants_molar_cc.min() + min_c_j = reactants_molar_cc.min() if min_c_j <= 1e-25: - (jdx, )=np.where(reactants_molar_cc == min_c_j) - reactants_molar_cc[jdx]=1.0 # any non-zero value will do since rb_i will be zero - - - - for jdx in range(rxn_idx_alpha_mtrx[1,:].size): + (jdx,) = np.where(reactants_molar_cc == min_c_j) + reactants_molar_cc[jdx] = ( + 1.0 # any non-zero value will do since rb_i will be zero + ) + for jdx in range(rxn_idx_alpha_mtrx[1, :].size): c_j = reactants_molar_cc[jdx] - d_alpha_d_kf_ri_mtrx[rxn_idx, jdx_start + jdx] = spc_cc_power_prod * math.log(c_j) * rxn_idx_dalpha_dtheta_mtrx[1, jdx] * dkf_dtheta_vec [rxn_idx] - + d_alpha_d_kf_ri_mtrx[rxn_idx, jdx_start + jdx] = ( + spc_cc_power_prod + * math.log(c_j) + * rxn_idx_dalpha_dtheta_mtrx[1, jdx] + * dkf_dtheta_vec[rxn_idx] + ) - ''' + """ for (jdx, c_j) in enumerate(reactants_molar_cc): d_alpha_d_kf_ri_mtrx[idx, jdx_start + jdx] = spc_cc_power_prod * math.log(c_j) @@ -1607,19 +1754,20 @@ def d2ri_theta2_mtrx(self, rxn_idx, spc_molar_cc_vec, d_alpha_d_kf_ri_mtrx[idx, jdx_start + jdx] = dkf_dtheta_vec[idx] * math.log(c_j) * dalpha_dtheta_mtrx[1, jdx] * spc_cc_power_prod jdx_start += alpha_mtrx.shape[1] - ''' - #assert jdx_start == n_alphas, 'n_alphas = %r; sum = %r' % (n_alphas, jdx_start) + """ + # assert jdx_start == n_alphas, 'n_alphas = %r; sum = %r' % (n_alphas, jdx_start) # ------------------------------ # partial_beta(partial_kf r_i) # ------------------------------ if kf_vec is not None and beta_lst is not None: - - n_betas=0 + n_betas = 0 for beta_mtrx in beta_lst: n_betas += beta_mtrx.shape[1] - d_beta_d_kf_ri_mtrx= np.zeros((len(self.reactions), n_betas), dtype = np.float64) + d_beta_d_kf_ri_mtrx = np.zeros( + (len(self.reactions), n_betas), dtype=np.float64 + ) # ******************************************************************************************* # 2nd row block @@ -1628,54 +1776,55 @@ def d2ri_theta2_mtrx(self, rxn_idx, spc_molar_cc_vec, # partial_kb(partial_kb r_i) # --------------------------- if kb_vec is not None: - - d_kb_d_kb_ri_mtrx= np.zeros((len(self.reactions), len(self.reactions)), dtype = np.float64) + d_kb_d_kb_ri_mtrx = np.zeros( + (len(self.reactions), len(self.reactions)), dtype=np.float64 + ) theta_vec = copy.deepcopy(kb_vec) d2kb_dtheta2_vec = self.__d2phi_dtheta2(theta_vec, self.kb_bnds) - if beta_lst is None: - (_, beta_lst_local)=self.__get_power_law_exponents() + (_, beta_lst_local) = self.__get_power_law_exponents() else: - beta_lst_local= copy.deepcopy(beta_lst) + beta_lst_local = copy.deepcopy(beta_lst) beta_lst_local = self.perform_reparam(beta_lst_local, self.beta_bnds) + beta_mtrx = beta_lst_local[rxn_idx] + products_ids = beta_mtrx[0, :].astype(int) + products_molar_cc = spc_molar_cc_vec[products_ids] - beta_mtrx=beta_lst_local[rxn_idx] - products_ids=beta_mtrx[0, :].astype(int) - - products_molar_cc=spc_molar_cc_vec[products_ids] - - spc_cc_power_prod=np.prod(products_molar_cc**beta_mtrx[1, :]) + spc_cc_power_prod = np.prod(products_molar_cc ** beta_mtrx[1, :]) - d_kb_d_kb_ri_mtrx[rxn_idx, rxn_idx]=- \ - d2kb_dtheta2_vec[rxn_idx] * spc_cc_power_prod + d_kb_d_kb_ri_mtrx[rxn_idx, rxn_idx] = ( + -d2kb_dtheta2_vec[rxn_idx] * spc_cc_power_prod + ) # ------------------------------ # partial_alpha(partial_kb r_i) # ------------------------------ if kb_vec is not None and alpha_lst is not None: - - n_alphas=0 + n_alphas = 0 for alpha_mtrx in alpha_lst: n_alphas += alpha_mtrx.shape[1] - d_alpha_d_kb_ri_mtrx= np.zeros((len(self.reactions), n_alphas), dtype = np.float64) + d_alpha_d_kb_ri_mtrx = np.zeros( + (len(self.reactions), n_alphas), dtype=np.float64 + ) # ------------------------------ # partial_beta(partial_kb r_i) # ------------------------------ if kb_vec is not None and beta_lst is not None: - - n_betas=0 + n_betas = 0 for beta_mtrx in beta_lst: n_betas += beta_mtrx.shape[1] - d_beta_d_kb_ri_mtrx= np.zeros((len(self.reactions), n_betas), dtype = np.float64) + d_beta_d_kb_ri_mtrx = np.zeros( + (len(self.reactions), n_betas), dtype=np.float64 + ) theta_lst = copy.deepcopy(beta_lst) theta_vec = copy.deepcopy(kb_vec) @@ -1683,41 +1832,43 @@ def d2ri_theta2_mtrx(self, rxn_idx, spc_molar_cc_vec, dbeta_dtheta_lst = self.__dphi_dtheta(theta_lst, self.beta_bnds) dkb_dtheta_vec = self.__dphi_dtheta(kb_vec, self.kb_bnds) - - beta_lst_local=copy.deepcopy(beta_lst) + beta_lst_local = copy.deepcopy(beta_lst) beta_lst_local = self.perform_reparam(beta_lst_local, self.beta_bnds) - #kb_vec= self.perform_reparam(kb_vec, self.kb_bnds) + # kb_vec= self.perform_reparam(kb_vec, self.kb_bnds) - jdx_start=0 + jdx_start = 0 for idx in range(rxn_idx): - beta_mtrx = beta_lst_local[idx] jdx_start += beta_mtrx.shape[1] rxn_idx_beta_mtrx = beta_lst_local[rxn_idx] rxn_idx_dbeta_dtheta_mtrx = dbeta_dtheta_lst[rxn_idx] - products_ids=rxn_idx_beta_mtrx[0, :].astype(int) + products_ids = rxn_idx_beta_mtrx[0, :].astype(int) - products_molar_cc=spc_molar_cc_vec[products_ids] + products_molar_cc = spc_molar_cc_vec[products_ids] + spc_cc_power_prod = -np.prod(products_molar_cc ** rxn_idx_beta_mtrx[1, :]) - spc_cc_power_prod= - np.prod(products_molar_cc**rxn_idx_beta_mtrx[1, :]) - - min_c_j=products_molar_cc.min() + min_c_j = products_molar_cc.min() if min_c_j <= 1e-25: - (jdx, )=np.where(products_molar_cc == min_c_j) - products_molar_cc[jdx]=1.0 # any non-zero value will do since rb_i will be zero - - for jdx in range(rxn_idx_beta_mtrx[1,:].size): - + (jdx,) = np.where(products_molar_cc == min_c_j) + products_molar_cc[jdx] = ( + 1.0 # any non-zero value will do since rb_i will be zero + ) + for jdx in range(rxn_idx_beta_mtrx[1, :].size): c_j = products_molar_cc[jdx] - d_beta_d_kb_ri_mtrx[rxn_idx, jdx_start + jdx] = spc_cc_power_prod * math.log(c_j) * rxn_idx_dbeta_dtheta_mtrx[1, jdx] * dkb_dtheta_vec[rxn_idx] - - #assert jdx_start == n_betas, 'n_betas = %r; sum = %r' % ( + d_beta_d_kb_ri_mtrx[rxn_idx, jdx_start + jdx] = ( + spc_cc_power_prod + * math.log(c_j) + * rxn_idx_dbeta_dtheta_mtrx[1, jdx] + * dkb_dtheta_vec[rxn_idx] + ) + + # assert jdx_start == n_betas, 'n_betas = %r; sum = %r' % ( # n_betas, jdx_start) # ******************************************************************************************* @@ -1727,12 +1878,11 @@ def d2ri_theta2_mtrx(self, rxn_idx, spc_molar_cc_vec, # partial_alpha(partial_alpha r_i) # --------------------------------- if alpha_lst is not None: - - n_alphas=0 + n_alphas = 0 for alpha_mtrx in alpha_lst: n_alphas += alpha_mtrx.shape[1] - d_alpha_d_alpha_ri_mtrx= np.zeros((n_alphas, n_alphas), dtype = np.float64) + d_alpha_d_alpha_ri_mtrx = np.zeros((n_alphas, n_alphas), dtype=np.float64) theta_lst = copy.deepcopy(alpha_lst) @@ -1742,79 +1892,79 @@ def d2ri_theta2_mtrx(self, rxn_idx, spc_molar_cc_vec, dalpha_dtheta_lst = self.__dphi_dtheta(theta_lst, self.alpha_bnds) - alpha_lst_local=copy.deepcopy(alpha_lst) + alpha_lst_local = copy.deepcopy(alpha_lst) if kf_vec is None: kf_vec_local = self.__get_kf() else: - kf_vec_local= copy.deepcopy(kf_vec) + kf_vec_local = copy.deepcopy(kf_vec) kf_vec_local = self.perform_reparam(kf_vec_local, self.kf_bnds) - jdx_start=0 - idx_start=0 + jdx_start = 0 + idx_start = 0 for idx in range(rxn_idx): - - alpha_mtrx=alpha_lst_local[idx] + alpha_mtrx = alpha_lst_local[idx] jdx_start += alpha_mtrx.shape[1] alpha_mtrx = alpha_lst_local[rxn_idx] - reactants_ids=alpha_mtrx[0, :].astype(int) + reactants_ids = alpha_mtrx[0, :].astype(int) - reactants_molar_cc=spc_molar_cc_vec[reactants_ids] + reactants_molar_cc = spc_molar_cc_vec[reactants_ids] - spc_cc_power_prod=np.prod(reactants_molar_cc**alpha_mtrx[1, :]) + spc_cc_power_prod = np.prod(reactants_molar_cc ** alpha_mtrx[1, :]) - rf_i=kf_vec_local[rxn_idx] * spc_cc_power_prod + rf_i = kf_vec_local[rxn_idx] * spc_cc_power_prod - min_c_j=reactants_molar_cc.min() + min_c_j = reactants_molar_cc.min() if min_c_j <= 1e-25: - (jdx, )=np.where(reactants_molar_cc == min_c_j) - reactants_molar_cc[jdx]=1.0 # any non-zero value will do since rb_i will be zero + (jdx,) = np.where(reactants_molar_cc == min_c_j) + reactants_molar_cc[jdx] = ( + 1.0 # any non-zero value will do since rb_i will be zero + ) dalpha_dtheta_mtrx = dalpha_dtheta_lst[rxn_idx] d2alpha_dtheta2_mtrx = d2alpha_dtheta2_lst[rxn_idx] - for jdx in range(alpha_mtrx[1,:].size): - + for jdx in range(alpha_mtrx[1, :].size): c_j = reactants_molar_cc[jdx] - dalpha_j_dtheta_j = dalpha_dtheta_mtrx[1 , jdx] - d2alpha_dtheta2 = d2alpha_dtheta2_mtrx[1 , jdx] + dalpha_j_dtheta_j = dalpha_dtheta_mtrx[1, jdx] + d2alpha_dtheta2 = d2alpha_dtheta2_mtrx[1, jdx] - for Jdx in range(alpha_mtrx[1,:].size): - - - dalpha_J_dtheta_J = dalpha_dtheta_mtrx[1 , Jdx] + for Jdx in range(alpha_mtrx[1, :].size): + dalpha_J_dtheta_J = dalpha_dtheta_mtrx[1, Jdx] c_J = reactants_molar_cc[Jdx] d2r_dalpha2 = rf_i * math.log(c_j) * math.log(c_J) dr_dtheta_prod = dalpha_j_dtheta_j * dalpha_J_dtheta_J if jdx == Jdx: - - d_alpha_d_alpha_ri_mtrx[jdx_start + jdx, jdx_start + Jdx] = d2r_dalpha2 * dr_dtheta_prod + rf_i * math.log(c_j) * d2alpha_dtheta2 + d_alpha_d_alpha_ri_mtrx[jdx_start + jdx, jdx_start + Jdx] = ( + d2r_dalpha2 * dr_dtheta_prod + + rf_i * math.log(c_j) * d2alpha_dtheta2 + ) else: - - d_alpha_d_alpha_ri_mtrx[jdx_start + jdx, jdx_start + Jdx] = d2r_dalpha2 * dr_dtheta_prod + d_alpha_d_alpha_ri_mtrx[jdx_start + jdx, jdx_start + Jdx] = ( + d2r_dalpha2 * dr_dtheta_prod + ) # -------------------------------- # partial_beta(partial_alpha r_i) # -------------------------------- if alpha_lst is not None and beta_lst is not None: - - n_alphas=0 + n_alphas = 0 for alpha_mtrx in alpha_lst: n_alphas += alpha_mtrx.shape[1] - n_betas=0 + n_betas = 0 for beta_mtrx in beta_lst: n_betas += beta_mtrx.shape[1] - d_beta_d_alpha_ri_mtrx= np.zeros((n_alphas, n_betas), dtype = np.float64) + d_beta_d_alpha_ri_mtrx = np.zeros((n_alphas, n_betas), dtype=np.float64) # ******************************************************************************************* # 4th row block @@ -1823,12 +1973,11 @@ def d2ri_theta2_mtrx(self, rxn_idx, spc_molar_cc_vec, # partial_beta(partial_beta r_i) # --------------------------------- if beta_lst is not None: - - n_betas=0 + n_betas = 0 for beta_mtrx in beta_lst: n_betas += beta_mtrx.shape[1] - d_beta_d_beta_ri_mtrx = np.zeros((n_betas, n_betas), dtype = np.float64) + d_beta_d_beta_ri_mtrx = np.zeros((n_betas, n_betas), dtype=np.float64) theta_lst = copy.deepcopy(beta_lst) @@ -1848,175 +1997,266 @@ def d2ri_theta2_mtrx(self, rxn_idx, spc_molar_cc_vec, kb_vec_local = self.perform_reparam(kb_vec_local, self.kb_bnds) - jdx_start=0 - idx_start=0 + jdx_start = 0 + idx_start = 0 for idx in range(rxn_idx): beta_mtrx = beta_lst_local[idx] jdx_start += beta_mtrx.shape[1] - beta_mtrx=beta_lst_local[rxn_idx] + beta_mtrx = beta_lst_local[rxn_idx] - products_ids=beta_mtrx[0, :].astype(int) + products_ids = beta_mtrx[0, :].astype(int) - products_molar_cc=spc_molar_cc_vec[products_ids] + products_molar_cc = spc_molar_cc_vec[products_ids] - spc_cc_power_prod= np.prod(products_molar_cc**beta_mtrx[1, :]) + spc_cc_power_prod = np.prod(products_molar_cc ** beta_mtrx[1, :]) - rb_i=- kb_vec_local[rxn_idx] * spc_cc_power_prod + rb_i = -kb_vec_local[rxn_idx] * spc_cc_power_prod - min_c_j=products_molar_cc.min() + min_c_j = products_molar_cc.min() if min_c_j <= 1e-25: - (jdx, )=np.where(products_molar_cc == min_c_j) - products_molar_cc[jdx]=1.0 # any non-zero value will do since rb_i will be zero + (jdx,) = np.where(products_molar_cc == min_c_j) + products_molar_cc[jdx] = ( + 1.0 # any non-zero value will do since rb_i will be zero + ) dbeta_dtheta_mtrx = dbeta_dtheta_lst[rxn_idx] d2beta_dtheta2_mtrx = d2beta_dtheta2_lst[rxn_idx] - for jdx in range(beta_mtrx[1,:].size): - + for jdx in range(beta_mtrx[1, :].size): c_j = reactants_molar_cc[jdx] - dbeta_j_dtheta_j = dbeta_dtheta_mtrx[1 , jdx] - d2beta_dtheta2 = d2beta_dtheta2_mtrx[1 , jdx] + dbeta_j_dtheta_j = dbeta_dtheta_mtrx[1, jdx] + d2beta_dtheta2 = d2beta_dtheta2_mtrx[1, jdx] - for Jdx in range(beta_mtrx[1,:].size): - - dbeta_J_dtheta_J = dbeta_dtheta_mtrx[1 , Jdx] + for Jdx in range(beta_mtrx[1, :].size): + dbeta_J_dtheta_J = dbeta_dtheta_mtrx[1, Jdx] c_J = reactants_molar_cc[Jdx] d2r_dbeta2 = rb_i * math.log(c_j) * math.log(c_J) dr_dtheta_prod = dbeta_j_dtheta_j * dbeta_J_dtheta_J if jdx == Jdx: - - d_beta_d_beta_ri_mtrx[jdx_start + jdx, jdx_start + Jdx] = d2r_dbeta2 * dr_dtheta_prod + rb_i * math.log(c_j) * d2beta_dtheta2 + d_beta_d_beta_ri_mtrx[jdx_start + jdx, jdx_start + Jdx] = ( + d2r_dbeta2 * dr_dtheta_prod + + rb_i * math.log(c_j) * d2beta_dtheta2 + ) else: - - d_beta_d_beta_ri_mtrx[jdx_start + jdx, jdx_start + Jdx] = d2r_dbeta2 * dr_dtheta_prod + d_beta_d_beta_ri_mtrx[jdx_start + jdx, jdx_start + Jdx] = ( + d2r_dbeta2 * dr_dtheta_prod + ) # ******************************************************************************************* # Assembly # General case - if kf_vec is not None and kb_vec is not None and alpha_lst is not None and beta_lst is not None: - - hessian_ri_1st_row=np.hstack( - [d_kf_d_kf_ri_mtrx, d_kb_d_kf_ri_mtrx, d_alpha_d_kf_ri_mtrx, d_beta_d_kf_ri_mtrx]) - - hessian_ri_2nd_row=np.hstack([d_kb_d_kf_ri_mtrx.transpose( - ), d_kb_d_kb_ri_mtrx, d_alpha_d_kb_ri_mtrx, d_beta_d_kb_ri_mtrx]) - - hessian_ri_3rd_row=np.hstack([d_alpha_d_kf_ri_mtrx.transpose( - ), d_alpha_d_kb_ri_mtrx.transpose(), d_alpha_d_alpha_ri_mtrx, d_beta_d_alpha_ri_mtrx]) - - hessian_ri_4th_row=np.hstack([d_beta_d_kf_ri_mtrx.transpose(), d_beta_d_kb_ri_mtrx.transpose( - ), d_beta_d_alpha_ri_mtrx.transpose(), d_beta_d_beta_ri_mtrx]) - - hessian_ri=np.vstack( - [hessian_ri_1st_row, hessian_ri_2nd_row, hessian_ri_3rd_row, hessian_ri_4th_row]) + if ( + kf_vec is not None + and kb_vec is not None + and alpha_lst is not None + and beta_lst is not None + ): + hessian_ri_1st_row = np.hstack( + [ + d_kf_d_kf_ri_mtrx, + d_kb_d_kf_ri_mtrx, + d_alpha_d_kf_ri_mtrx, + d_beta_d_kf_ri_mtrx, + ] + ) + + hessian_ri_2nd_row = np.hstack( + [ + d_kb_d_kf_ri_mtrx.transpose(), + d_kb_d_kb_ri_mtrx, + d_alpha_d_kb_ri_mtrx, + d_beta_d_kb_ri_mtrx, + ] + ) + + hessian_ri_3rd_row = np.hstack( + [ + d_alpha_d_kf_ri_mtrx.transpose(), + d_alpha_d_kb_ri_mtrx.transpose(), + d_alpha_d_alpha_ri_mtrx, + d_beta_d_alpha_ri_mtrx, + ] + ) + + hessian_ri_4th_row = np.hstack( + [ + d_beta_d_kf_ri_mtrx.transpose(), + d_beta_d_kb_ri_mtrx.transpose(), + d_beta_d_alpha_ri_mtrx.transpose(), + d_beta_d_beta_ri_mtrx, + ] + ) + + hessian_ri = np.vstack( + [ + hessian_ri_1st_row, + hessian_ri_2nd_row, + hessian_ri_3rd_row, + hessian_ri_4th_row, + ] + ) # Forward case - elif kf_vec is not None and alpha_lst is not None and kb_vec is None and beta_lst is None: - - hessian_ri_1st_row=np.hstack( - [d_kf_d_kf_ri_mtrx, d_alpha_d_kf_ri_mtrx]) - hessian_ri_2nd_row=np.hstack( - [d_alpha_d_kf_ri_mtrx.transpose(), d_alpha_d_alpha_ri_mtrx]) - - hessian_ri=np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) + elif ( + kf_vec is not None + and alpha_lst is not None + and kb_vec is None + and beta_lst is None + ): + hessian_ri_1st_row = np.hstack([d_kf_d_kf_ri_mtrx, d_alpha_d_kf_ri_mtrx]) + hessian_ri_2nd_row = np.hstack( + [d_alpha_d_kf_ri_mtrx.transpose(), d_alpha_d_alpha_ri_mtrx] + ) + + hessian_ri = np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) # k's only case - elif kf_vec is not None and kb_vec is not None and alpha_lst is None and beta_lst is None: - - hessian_ri_1st_row=np.hstack( - [d_kf_d_kf_ri_mtrx, d_kb_d_kf_ri_mtrx]) - hessian_ri_2nd_row=np.hstack( - [d_kb_d_kf_ri_mtrx.transpose(), d_kb_d_kb_ri_mtrx]) - - hessian_ri=np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) + elif ( + kf_vec is not None + and kb_vec is not None + and alpha_lst is None + and beta_lst is None + ): + hessian_ri_1st_row = np.hstack([d_kf_d_kf_ri_mtrx, d_kb_d_kf_ri_mtrx]) + hessian_ri_2nd_row = np.hstack( + [d_kb_d_kf_ri_mtrx.transpose(), d_kb_d_kb_ri_mtrx] + ) + + hessian_ri = np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) # kfs only case - elif kf_vec is not None and kb_vec is None and alpha_lst is None and beta_lst is None: - - hessian_ri=d_kf_d_kf_ri_mtrx + elif ( + kf_vec is not None + and kb_vec is None + and alpha_lst is None + and beta_lst is None + ): + hessian_ri = d_kf_d_kf_ri_mtrx # k's and alphas only case - elif kf_vec is not None and kb_vec is not None and alpha_lst is not None and beta_lst is None: - - hessian_ri_1st_row=np.hstack( - [d_kf_d_kf_ri_mtrx, d_kb_d_kf_ri_mtrx, d_alpha_d_kf_ri_mtrx]) - hessian_ri_2nd_row=np.hstack( - [d_kb_d_kf_ri_mtrx.transpose(), d_kb_d_kb_ri_mtrx, d_alpha_d_kb_ri_mtrx]) - hessian_ri_3rd_row=np.hstack([d_alpha_d_kf_ri_mtrx.transpose( - ), d_alpha_d_kb_ri_mtrx.transpose(), d_alpha_d_alpha_ri_mtrx]) - - hessian_ri=np.vstack( - [hessian_ri_1st_row, hessian_ri_2nd_row, hessian_ri_3rd_row]) + elif ( + kf_vec is not None + and kb_vec is not None + and alpha_lst is not None + and beta_lst is None + ): + hessian_ri_1st_row = np.hstack( + [d_kf_d_kf_ri_mtrx, d_kb_d_kf_ri_mtrx, d_alpha_d_kf_ri_mtrx] + ) + hessian_ri_2nd_row = np.hstack( + [d_kb_d_kf_ri_mtrx.transpose(), d_kb_d_kb_ri_mtrx, d_alpha_d_kb_ri_mtrx] + ) + hessian_ri_3rd_row = np.hstack( + [ + d_alpha_d_kf_ri_mtrx.transpose(), + d_alpha_d_kb_ri_mtrx.transpose(), + d_alpha_d_alpha_ri_mtrx, + ] + ) + + hessian_ri = np.vstack( + [hessian_ri_1st_row, hessian_ri_2nd_row, hessian_ri_3rd_row] + ) # alphas only case - elif kf_vec is None and kb_vec is None and alpha_lst is not None and beta_lst is None: - - hessian_ri=d_alpha_d_alpha_ri_mtrx + elif ( + kf_vec is None + and kb_vec is None + and alpha_lst is not None + and beta_lst is None + ): + hessian_ri = d_alpha_d_alpha_ri_mtrx # betas only case - elif kf_vec is None and kb_vec is None and alpha_lst is None and beta_lst is not None: - - hessian_ri=d_beta_d_beta_ri_mtrx + elif ( + kf_vec is None + and kb_vec is None + and alpha_lst is None + and beta_lst is not None + ): + hessian_ri = d_beta_d_beta_ri_mtrx # alphas and betas only case - elif kf_vec is None and kb_vec is None and alpha_lst is not None and beta_lst is not None: - - hessian_ri_1st_row=np.hstack( - [d_alpha_d_alpha_ri_mtrx, d_beta_d_alpha_ri_mtrx]) - hessian_ri_2nd_row=np.hstack( - [d_beta_d_alpha_ri_mtrx.transpose(), d_beta_d_beta_ri_mtrx]) - - hessian_ri=np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) + elif ( + kf_vec is None + and kb_vec is None + and alpha_lst is not None + and beta_lst is not None + ): + hessian_ri_1st_row = np.hstack( + [d_alpha_d_alpha_ri_mtrx, d_beta_d_alpha_ri_mtrx] + ) + hessian_ri_2nd_row = np.hstack( + [d_beta_d_alpha_ri_mtrx.transpose(), d_beta_d_beta_ri_mtrx] + ) + + hessian_ri = np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) # kfs and betas only case - elif kf_vec is not None and kb_vec is None and alpha_lst is None and beta_lst is not None: - - hessian_ri_1st_row=np.hstack( - [d_kf_d_kf_ri_mtrx, d_beta_d_kf_ri_mtrx]) - hessian_ri_2nd_row=np.hstack( - [d_beta_d_kf_ri_mtrx.transpose(), d_beta_d_beta_ri_mtrx]) - - hessian_ri=np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) + elif ( + kf_vec is not None + and kb_vec is None + and alpha_lst is None + and beta_lst is not None + ): + hessian_ri_1st_row = np.hstack([d_kf_d_kf_ri_mtrx, d_beta_d_kf_ri_mtrx]) + hessian_ri_2nd_row = np.hstack( + [d_beta_d_kf_ri_mtrx.transpose(), d_beta_d_beta_ri_mtrx] + ) + + hessian_ri = np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) # kbs and alphas only case - elif kf_vec is None and kb_vec is not None and alpha_lst is not None and beta_lst is None: - - hessian_ri_1st_row=np.hstack( - [d_kb_d_kb_ri_mtrx, d_alpha_d_kb_ri_mtrx]) - hessian_ri_2nd_row=np.hstack( - [d_alpha_d_kb_ri_mtrx.transpose(), d_alpha_d_alpha_ri_mtrx]) - - hessian_ri=np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) + elif ( + kf_vec is None + and kb_vec is not None + and alpha_lst is not None + and beta_lst is None + ): + hessian_ri_1st_row = np.hstack([d_kb_d_kb_ri_mtrx, d_alpha_d_kb_ri_mtrx]) + hessian_ri_2nd_row = np.hstack( + [d_alpha_d_kb_ri_mtrx.transpose(), d_alpha_d_alpha_ri_mtrx] + ) + + hessian_ri = np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) else: - assert False, 'Hessian ri case not implemented.' + assert False, "Hessian ri case not implemented." return hessian_ri - def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, - theta_kf_vec = None, theta_kb_vec =None, - theta_alpha_lst=None, theta_beta_lst=None): - '''Second partial derivatives of the ith reaction rate law wrt parameters. + def d2ri_theta2_mtrx_new( + self, + rxn_idx, + spc_molar_cc_vec, + theta_kf_vec=None, + theta_kb_vec=None, + theta_alpha_lst=None, + theta_beta_lst=None, + ): + """Second partial derivatives of the ith reaction rate law wrt parameters. The parameters in the derivative are ordered as: k_fs, k_bs, alphas, betas. The matrix is p x p. Where p is the total number of parameters. That, is p = 2 * m + n_alpha + n_beta, where n_alpha is the number of active forward reaction species, and n_beta is the number of active reverse reaction species. - ''' + """ assert isinstance(rxn_idx, int) - assert 0 <= rxn_idx <= len(self.reactions), 'rxn_idx = %r'%(rxn_idx) + assert 0 <= rxn_idx <= len(self.reactions), "rxn_idx = %r" % (rxn_idx) assert isinstance(spc_molar_cc_vec, np.ndarray) assert spc_molar_cc_vec.size == len(self.species) - assert np.all(spc_molar_cc_vec >= 0), 'spc_molar_cc_vec =\n%r' % spc_molar_cc_vec + assert np.all(spc_molar_cc_vec >= 0), ( + "spc_molar_cc_vec =\n%r" % spc_molar_cc_vec + ) # ******************************************************************************************* # 1st row block partial_kf(r_i) @@ -2026,13 +2266,12 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # D2_theta_kf2(kf) diag(pi) # ---------------------------------------- if theta_kf_vec is not None: - # Compute D2_theta_kf2(kf) theta_kf_vec = copy.deepcopy(theta_kf_vec) - #print('theta_kf_vec=',theta_kf_vec) - #print('kf_bnds=',self.kf_bnds) + # print('theta_kf_vec=',theta_kf_vec) + # print('kf_bnds=',self.kf_bnds) d2kf_dtheta_kf2_vec = self.__d2phi_dtheta2(theta_kf_vec, self.kf_bnds) @@ -2053,13 +2292,15 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, spc_cc_power_prod = np.prod(active_spc_molar_cc**alpha_i_vec) p_i = spc_cc_power_prod - diag_pi = np.zeros((len(self.reactions), len(self.reactions)), dtype = np.float64) + diag_pi = np.zeros( + (len(self.reactions), len(self.reactions)), dtype=np.float64 + ) diag_pi[rxn_idx, rxn_idx] = p_i # Compute product - #print('\t d2kkf_dtheta_kf2_mtrx=\n','\t',d2kf_dtheta_kf2_mtrx) - #print('\t diag_pi=\n','\t',diag_pi) + # print('\t d2kkf_dtheta_kf2_mtrx=\n','\t',d2kf_dtheta_kf2_mtrx) + # print('\t diag_pi=\n','\t',diag_pi) d_theta_kf_d_theta_kf_ri_mtrx = d2kf_dtheta_kf2_mtrx @ diag_pi @@ -2067,15 +2308,15 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # partial_theta_kb(partial_theta_kf r_i) # -------------------------------------- if theta_kf_vec is not None and theta_kb_vec is not None: - - d_theta_kb_d_theta_kf_ri_mtrx = np.zeros((len(self.reactions), len(self.reactions)), dtype = np.float64) + d_theta_kb_d_theta_kf_ri_mtrx = np.zeros( + (len(self.reactions), len(self.reactions)), dtype=np.float64 + ) # ----------------------------------------- # partial_theta_alpha(partial_theta_kf r_i) = # partial_theta_kf(kf) P W_alpha_i partial_theta_alpha(alpha) # ----------------------------------------- if theta_kf_vec is not None and theta_alpha_lst is not None: - # Compute partial_theta_kf(kf) theta_kf_vec = copy.deepcopy(theta_kf_vec) @@ -2090,10 +2331,9 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, alpha_lst = self.perform_reparam(theta_alpha_lst, self.alpha_bnds) - p_vec = np.zeros(len(self.reactions), dtype = np.float64) - - for (irxn, alpha_data_mtrx) in enumerate(alpha_lst): + p_vec = np.zeros(len(self.reactions), dtype=np.float64) + for irxn, alpha_data_mtrx in enumerate(alpha_lst): active_spc_ids = alpha_data_mtrx[0, :].astype(int) alpha_i_vec = alpha_data_mtrx[1, :] @@ -2108,7 +2348,7 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # Compute W_alpha_i where i is rxn_idx - n_alphas=0 + n_alphas = 0 for alpha_data_mtrx in alpha_lst: n_alphas += alpha_data_mtrx.shape[1] @@ -2124,62 +2364,78 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, min_c_j = active_spc_molar_cc.min() if min_c_j <= 1e-25: - (jdx, ) = np.where(active_spc_molar_cc == min_c_j) - active_spc_molar_cc[jdx] = 1.0 # any non-zero value will do since rf_i will be zero + (jdx,) = np.where(active_spc_molar_cc == min_c_j) + active_spc_molar_cc[jdx] = ( + 1.0 # any non-zero value will do since rf_i will be zero + ) w_alpha_i_vec = np.log(active_spc_molar_cc) - w_alpha_i_mtrx = np.zeros((len(self.reactions), n_alphas), dtype = np.float64) + w_alpha_i_mtrx = np.zeros((len(self.reactions), n_alphas), dtype=np.float64) - w_alpha_i_column_id = \ - sum([alpha_data_mtrx.shape[1] for alpha_data_mtrx in alpha_lst[:rxn_idx]]) + w_alpha_i_column_id = sum( + [alpha_data_mtrx.shape[1] for alpha_data_mtrx in alpha_lst[:rxn_idx]] + ) - end_plus_one = w_alpha_i_column_id + w_alpha_i_vec.size # end + 1 + end_plus_one = w_alpha_i_column_id + w_alpha_i_vec.size # end + 1 assert end_plus_one <= n_alphas # insertion - #print('n_alphas ', n_alphas) - #print('alpha_data_mtrx\n ', alpha_data_mtrx) - #print('w_alpha_i_mtrx.shape ', w_alpha_i_mtrx.shape) - #print('w_alpha_i_mtrx ', w_alpha_i_mtrx) - #print('w_alpha_i_column_id ', w_alpha_i_column_id) - #print('end ', end) + # print('n_alphas ', n_alphas) + # print('alpha_data_mtrx\n ', alpha_data_mtrx) + # print('w_alpha_i_mtrx.shape ', w_alpha_i_mtrx.shape) + # print('w_alpha_i_mtrx ', w_alpha_i_mtrx) + # print('w_alpha_i_column_id ', w_alpha_i_column_id) + # print('end ', end) w_alpha_i_mtrx[rxn_idx, w_alpha_i_column_id:end_plus_one] = w_alpha_i_vec[:] # Compute partial_theta_alpha(alpha) - dalpha_dtheta_alpha_lst = self.__dphi_dtheta(theta_alpha_lst, self.alpha_bnds) + dalpha_dtheta_alpha_lst = self.__dphi_dtheta( + theta_alpha_lst, self.alpha_bnds + ) for dalpha_dtheta_alpha_data_mtrx in dalpha_dtheta_alpha_lst: - - #assert(dalpha_dtheta_alpha_data_mtrx[0,:] == alpha_mtrx[0,:]) # reactant IDs must match - dalpha_dtheta_alpha_mtrx_i = np.diag(dalpha_dtheta_alpha_data_mtrx[1,:]) + # assert(dalpha_dtheta_alpha_data_mtrx[0,:] == alpha_mtrx[0,:]) # reactant IDs must match + dalpha_dtheta_alpha_mtrx_i = np.diag( + dalpha_dtheta_alpha_data_mtrx[1, :] + ) try: - dalpha_dtheta_alpha_mtrx = sp.linalg.block_diag(dalpha_dtheta_alpha_mtrx, dalpha_dtheta_alpha_mtrx_i) + dalpha_dtheta_alpha_mtrx = sp.linalg.block_diag( + dalpha_dtheta_alpha_mtrx, dalpha_dtheta_alpha_mtrx_i + ) except NameError: - dalpha_dtheta_alpha_mtrx = sp.linalg.block_diag(dalpha_dtheta_alpha_mtrx_i) + dalpha_dtheta_alpha_mtrx = sp.linalg.block_diag( + dalpha_dtheta_alpha_mtrx_i + ) # Compute product - d_theta_alpha_d_theta_kf_ri_mtrx = dkf_dtheta_mtrx @ p_mtrx @ w_alpha_i_mtrx @ dalpha_dtheta_alpha_mtrx + d_theta_alpha_d_theta_kf_ri_mtrx = ( + dkf_dtheta_mtrx @ p_mtrx @ w_alpha_i_mtrx @ dalpha_dtheta_alpha_mtrx + ) del dalpha_dtheta_alpha_mtrx - assert d_theta_alpha_d_theta_kf_ri_mtrx.shape == (len(self.reactions), n_alphas) + assert d_theta_alpha_d_theta_kf_ri_mtrx.shape == ( + len(self.reactions), + n_alphas, + ) # ---------------------------------------- # partial_theta_beta(partial_theta_kf r_i) # ---------------------------------------- if theta_kf_vec is not None and theta_beta_lst is not None: - - n_betas=0 + n_betas = 0 for theta_beta_data_mtrx in theta_beta_lst: n_betas += theta_beta_data_mtrx.shape[1] - d_theta_beta_d_theta_kf_ri_mtrx = np.zeros((len(self.reactions), n_betas), dtype = np.float64) + d_theta_beta_d_theta_kf_ri_mtrx = np.zeros( + (len(self.reactions), n_betas), dtype=np.float64 + ) # ******************************************************************************************* # 2nd row block partial_kb(r_i) @@ -2188,7 +2444,6 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # partial_theta_kf(partial_theta_kb r_i) # -------------------------------------- if theta_kb_vec is not None and theta_kf_vec is not None: - d_theta_kf_d_theta_kb_ri_mtrx = d_theta_kb_d_theta_kf_ri_mtrx.transpose() # ---------------------------------------- @@ -2196,7 +2451,6 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # - D2_theta_kb2(kb) diag(qi) # ---------------------------------------- if theta_kb_vec is not None: - # Compute D2_theta_kb2(kb) theta_kb_vec = copy.deepcopy(theta_kb_vec) @@ -2220,30 +2474,32 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, spc_cc_power_prod = np.prod(active_spc_molar_cc**beta_i_vec) q_i = spc_cc_power_prod - diag_qi = np.zeros((len(self.reactions), len(self.reactions)), dtype = np.float64) + diag_qi = np.zeros( + (len(self.reactions), len(self.reactions)), dtype=np.float64 + ) diag_qi[rxn_idx, rxn_idx] = q_i # Compute product - d_theta_kb_d_theta_kb_ri_mtrx = - d2kb_dtheta_kb2_mtrx @ diag_qi + d_theta_kb_d_theta_kb_ri_mtrx = -d2kb_dtheta_kb2_mtrx @ diag_qi # ----------------------------------------- # partial_theta_alpha(partial_theat_kb r_i) # ----------------------------------------- if theta_kb_vec is not None and theta_alpha_lst is not None: - - n_alphas=0 + n_alphas = 0 for theta_alpha_data_mtrx in theta_alpha_lst: n_alphas += theta_alpha_data_mtrx.shape[1] - d_theta_alpha_d_theta_kb_ri_mtrx= np.zeros((len(self.reactions), n_alphas), dtype = np.float64) + d_theta_alpha_d_theta_kb_ri_mtrx = np.zeros( + (len(self.reactions), n_alphas), dtype=np.float64 + ) # ---------------------------------------- # partial_theta_beta(partial_theta_kb r_i) = # - partial_theta_kb(kb) Q W_beta_i partial_theta_beta(beta) # ---------------------------------------- if theta_kb_vec is not None and theta_beta_lst is not None: - # Compute partial_theta_kb(kb) theta_kb_vec = copy.deepcopy(theta_kb_vec) @@ -2258,10 +2514,9 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, beta_lst = self.perform_reparam(theta_beta_lst, self.beta_bnds) - q_vec = np.zeros(len(self.reactions), dtype = np.float64) - - for (irxn, beta_data_mtrx) in enumerate(beta_lst): + q_vec = np.zeros(len(self.reactions), dtype=np.float64) + for irxn, beta_data_mtrx in enumerate(beta_lst): active_spc_ids = beta_data_mtrx[0, :].astype(int) beta_i_vec = beta_data_mtrx[1, :] @@ -2276,7 +2531,7 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # Compute W_beta_i where i is rxn_idx - n_betas=0 + n_betas = 0 for beta_data_mtrx in beta_lst: n_betas += beta_data_mtrx.shape[1] @@ -2292,15 +2547,18 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, min_c_j = active_spc_molar_cc.min() if min_c_j <= 1e-25: - (jdx, ) = np.where(active_spc_molar_cc == min_c_j) - active_spc_molar_cc[jdx] = 1.0 # any non-zero value will do since rf_i will be zero + (jdx,) = np.where(active_spc_molar_cc == min_c_j) + active_spc_molar_cc[jdx] = ( + 1.0 # any non-zero value will do since rf_i will be zero + ) w_beta_i_vec = np.log(active_spc_molar_cc) - w_beta_i_mtrx = np.zeros((len(self.reactions), n_betas), dtype = np.float64) + w_beta_i_mtrx = np.zeros((len(self.reactions), n_betas), dtype=np.float64) - w_beta_i_column_id = \ - sum([beta_data_mtrx.shape[1] for beta_data_mtrx in beta_lst[:rxn_idx]]) + w_beta_i_column_id = sum( + [beta_data_mtrx.shape[1] for beta_data_mtrx in beta_lst[:rxn_idx]] + ) end_plus_one = w_beta_i_column_id + w_beta_i_vec.size # end + 1 @@ -2314,22 +2572,30 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, dbeta_dtheta_beta_lst = self.__dphi_dtheta(theta_beta_lst, self.beta_bnds) for dbeta_dtheta_beta_data_mtrx in dbeta_dtheta_beta_lst: - - #assert(dbeta_dtheta_beta_data_mtrx[0,:] == beta_mtrx[0,:]) # reactant IDs must match - dbeta_dtheta_beta_mtrx_i = np.diag(dbeta_dtheta_beta_data_mtrx[1,:]) + # assert(dbeta_dtheta_beta_data_mtrx[0,:] == beta_mtrx[0,:]) # reactant IDs must match + dbeta_dtheta_beta_mtrx_i = np.diag(dbeta_dtheta_beta_data_mtrx[1, :]) try: - dbeta_dtheta_beta_mtrx = sp.linalg.block_diag(dbeta_dtheta_beta_mtrx, dbeta_dtheta_beta_mtrx_i) + dbeta_dtheta_beta_mtrx = sp.linalg.block_diag( + dbeta_dtheta_beta_mtrx, dbeta_dtheta_beta_mtrx_i + ) except NameError: - dbeta_dtheta_beta_mtrx = sp.linalg.block_diag(dbeta_dtheta_beta_mtrx_i) + dbeta_dtheta_beta_mtrx = sp.linalg.block_diag( + dbeta_dtheta_beta_mtrx_i + ) # Compute product - d_theta_beta_d_theta_kb_ri_mtrx = - dkb_dtheta_mtrx @ q_mtrx @ w_beta_i_mtrx @ dbeta_dtheta_beta_mtrx + d_theta_beta_d_theta_kb_ri_mtrx = ( + -dkb_dtheta_mtrx @ q_mtrx @ w_beta_i_mtrx @ dbeta_dtheta_beta_mtrx + ) del dbeta_dtheta_beta_mtrx - assert d_theta_beta_d_theta_kf_ri_mtrx.shape == (len(self.reactions), n_betas) + assert d_theta_beta_d_theta_kf_ri_mtrx.shape == ( + len(self.reactions), + n_betas, + ) # ******************************************************************************************* # 3rd row block @@ -2338,22 +2604,23 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # partial_theta_kf(partial_theta_alpha r_i) # ----------------------------------------- if theta_alpha_lst is not None and theta_kf_vec is not None: - - d_theta_kf_d_theta_alpha_ri_mtrx = d_theta_alpha_d_theta_kf_ri_mtrx.transpose() + d_theta_kf_d_theta_alpha_ri_mtrx = ( + d_theta_alpha_d_theta_kf_ri_mtrx.transpose() + ) # ----------------------------------------- # partial_theta_kb(partial_theta_alpha r_i) # ----------------------------------------- if theta_alpha_lst is not None and theta_kb_vec is not None: - - d_theta_kb_d_theta_alpha_ri_mtrx = d_theta_alpha_d_theta_kb_ri_mtrx.transpose() + d_theta_kb_d_theta_alpha_ri_mtrx = ( + d_theta_alpha_d_theta_kb_ri_mtrx.transpose() + ) # -------------------------------------------- # partial_theta_alpha(partial_theta_alpha r_i) = # rfi ((partial_theta_alpha(alpha))^2 W_alpha_iT W_alpha_i + D2_theta_alpha2(alpha) Diag(w_alpha_i) # -------------------------------------------- if theta_alpha_lst is not None: - # Compute rf_i where i is rxn_idx # get kf @@ -2382,19 +2649,28 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # Compute (partial_theta_alpha(alpha))^2 - dalpha_theta_alpha_lst = self.__dphi_dtheta(theta_alpha_lst, self.alpha_bnds) + dalpha_theta_alpha_lst = self.__dphi_dtheta( + theta_alpha_lst, self.alpha_bnds + ) for dalpha_dtheta_alpha_data_mtrx in dalpha_theta_alpha_lst: - - #assert(dalpha_dtheta_alpha_data_mtrx[0,:] == alpha_data_mtrx[0,:]) # reactant IDs must match - dalpha_dtheta_alpha_mtrx_i = np.diag(dalpha_dtheta_alpha_data_mtrx[1,:]) + # assert(dalpha_dtheta_alpha_data_mtrx[0,:] == alpha_data_mtrx[0,:]) # reactant IDs must match + dalpha_dtheta_alpha_mtrx_i = np.diag( + dalpha_dtheta_alpha_data_mtrx[1, :] + ) try: - dalpha_dtheta_alpha_mtrx = sp.linalg.block_diag(dalpha_dtheta_alpha_mtrx, dalpha_dtheta_alpha_mtrx_i) + dalpha_dtheta_alpha_mtrx = sp.linalg.block_diag( + dalpha_dtheta_alpha_mtrx, dalpha_dtheta_alpha_mtrx_i + ) except NameError: - dalpha_dtheta_alpha_mtrx = sp.linalg.block_diag(dalpha_dtheta_alpha_mtrx_i) + dalpha_dtheta_alpha_mtrx = sp.linalg.block_diag( + dalpha_dtheta_alpha_mtrx_i + ) - dalpha_dtheta_alpha_mtrx_pwr2 = dalpha_dtheta_alpha_mtrx @ dalpha_dtheta_alpha_mtrx + dalpha_dtheta_alpha_mtrx_pwr2 = ( + dalpha_dtheta_alpha_mtrx @ dalpha_dtheta_alpha_mtrx + ) del dalpha_dtheta_alpha_mtrx @@ -2402,7 +2678,7 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # get W_alpha_i - n_alphas=0 + n_alphas = 0 for alpha_data_mtrx in alpha_lst: n_alphas += alpha_data_mtrx.shape[1] @@ -2418,17 +2694,20 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, min_c_j = active_spc_molar_cc.min() if min_c_j <= 1e-25: - (jdx, ) = np.where(active_spc_molar_cc == min_c_j) - active_spc_molar_cc[jdx] = 1.0 # any non-zero value will do since rf_i will be zero + (jdx,) = np.where(active_spc_molar_cc == min_c_j) + active_spc_molar_cc[jdx] = ( + 1.0 # any non-zero value will do since rf_i will be zero + ) w_alpha_i_vec = np.log(active_spc_molar_cc) - w_alpha_i_mtrx = np.zeros((len(self.reactions), n_alphas), dtype = np.float64) + w_alpha_i_mtrx = np.zeros((len(self.reactions), n_alphas), dtype=np.float64) - w_alpha_i_column_id = \ - sum([alpha_data_mtrx.shape[1] for alpha_data_mtrx in alpha_lst[:rxn_idx]]) + w_alpha_i_column_id = sum( + [alpha_data_mtrx.shape[1] for alpha_data_mtrx in alpha_lst[:rxn_idx]] + ) - end_plus_one = w_alpha_i_column_id + w_alpha_i_vec.size # end + 1 + end_plus_one = w_alpha_i_column_id + w_alpha_i_vec.size # end + 1 assert end_plus_one <= n_alphas @@ -2442,11 +2721,12 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, d2alpha_dtheta2_lst = self.__d2phi_dtheta2(theta_alpha_lst, self.alpha_bnds) for d2alpha_dtheta2_data_mtrx in d2alpha_dtheta2_lst: - - d2alpha_dtheta2_mtrx_i = np.diag(d2alpha_dtheta2_data_mtrx[1,:]) + d2alpha_dtheta2_mtrx_i = np.diag(d2alpha_dtheta2_data_mtrx[1, :]) try: - d2alpha_dtheta2_mtrx = sp.linalg.block_diag(d2alpha_dtheta2_mtrx, d2alpha_dtheta2_mtrx_i) + d2alpha_dtheta2_mtrx = sp.linalg.block_diag( + d2alpha_dtheta2_mtrx, d2alpha_dtheta2_mtrx_i + ) except NameError: d2alpha_dtheta2_mtrx = sp.linalg.block_diag(d2alpha_dtheta2_mtrx_i) @@ -2458,19 +2738,18 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # Compute product - #print('dalpha_dtheta_alpha_mtrx_pwr2 \n', dalpha_dtheta_alpha_mtrx_pwr2) - #print('w_alpha_i_T_w_alpha_i_mtrx \n', w_alpha_i_T_w_alpha_i_mtrx) + # print('dalpha_dtheta_alpha_mtrx_pwr2 \n', dalpha_dtheta_alpha_mtrx_pwr2) + # print('w_alpha_i_T_w_alpha_i_mtrx \n', w_alpha_i_T_w_alpha_i_mtrx) - d_theta_alpha_d_theta_alpha_ri_mtrx = \ - rf_i * (dalpha_dtheta_alpha_mtrx_pwr2 @ w_alpha_i_T_w_alpha_i_mtrx \ - + \ - d2alpha_dtheta2_mtrx @ diag_w_alpha_i_irow) + d_theta_alpha_d_theta_alpha_ri_mtrx = rf_i * ( + dalpha_dtheta_alpha_mtrx_pwr2 @ w_alpha_i_T_w_alpha_i_mtrx + + d2alpha_dtheta2_mtrx @ diag_w_alpha_i_irow + ) # ------------------------------------------- # partial_theta_beta(partial_theta_alpha r_i) # ------------------------------------------- if theta_alpha_lst is not None and theta_beta_lst is not None: - n_alphas = 0 for theta_alpha_data_mtrx in theta_alpha_lst: n_alphas += theta_alpha_data_mtrx.shape[1] @@ -2479,7 +2758,9 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, for theta_beta_data_mtrx in theta_beta_lst: n_betas += theta_beta_data_mtrx.shape[1] - d_theta_beta_d_theta_alpha_ri_mtrx = np.zeros((n_alphas, n_betas), dtype = np.float64) + d_theta_beta_d_theta_alpha_ri_mtrx = np.zeros( + (n_alphas, n_betas), dtype=np.float64 + ) # ******************************************************************************************* # 4th row block @@ -2488,29 +2769,31 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # partial_theta_kf(partial_theta_beta r_i) # ---------------------------------------- if theta_beta_lst is not None and theta_kf_vec is not None: - - d_theta_kf_d_theta_beta_ri_mtrx = d_theta_beta_d_theta_kf_ri_mtrx.transpose() + d_theta_kf_d_theta_beta_ri_mtrx = ( + d_theta_beta_d_theta_kf_ri_mtrx.transpose() + ) # ---------------------------------------- # partial_theta_kb(partial_theta_beta r_i) # ---------------------------------------- if theta_beta_lst is not None and theta_kf_vec is not None: - - d_theta_kb_d_theta_beta_ri_mtrx = d_theta_beta_d_theta_kb_ri_mtrx.transpose() + d_theta_kb_d_theta_beta_ri_mtrx = ( + d_theta_beta_d_theta_kb_ri_mtrx.transpose() + ) # ------------------------------------------- # partial_theta_alpha(partial_theta_beta r_i) # ------------------------------------------- if theta_beta_lst is not None and theta_alpha_lst is not None: - - d_theta_alpha_d_theta_beta_ri_mtrx = d_theta_beta_d_theta_alpha_ri_mtrx.transpose() + d_theta_alpha_d_theta_beta_ri_mtrx = ( + d_theta_beta_d_theta_alpha_ri_mtrx.transpose() + ) # -------------------------------------------- # partial_theta_beta(partial_theta_beta r_i) = # - rbi ((partial_theta_beta(beta))^2 W_beta_iT W_beta_i + D2_theta_beta2(beta) Diag(w_beta_i) # -------------------------------------------- if theta_beta_lst is not None: - # Compute rb_i where i is rxn_idx # get kb @@ -2542,16 +2825,21 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, dbeta_dtheta_beta_lst = self.__dphi_dtheta(theta_beta_lst, self.beta_bnds) for dbeta_dtheta_beta_data_mtrx in dbeta_dtheta_beta_lst: - - #assert(dbeta_dtheta_beta_data_mtrx[0,:] == beta_data_mtrx[0,:]) # reactant IDs must match - dbeta_dtheta_beta_mtrx_i = np.diag(dbeta_dtheta_beta_data_mtrx[1,:]) + # assert(dbeta_dtheta_beta_data_mtrx[0,:] == beta_data_mtrx[0,:]) # reactant IDs must match + dbeta_dtheta_beta_mtrx_i = np.diag(dbeta_dtheta_beta_data_mtrx[1, :]) try: - dbeta_dtheta_beta_mtrx = sp.linalg.block_diag(dbeta_dtheta_beta_mtrx, dbeta_dtheta_beta_mtrx_i) + dbeta_dtheta_beta_mtrx = sp.linalg.block_diag( + dbeta_dtheta_beta_mtrx, dbeta_dtheta_beta_mtrx_i + ) except NameError: - dbeta_dtheta_beta_mtrx = sp.linalg.block_diag(dbeta_dtheta_beta_mtrx_i) + dbeta_dtheta_beta_mtrx = sp.linalg.block_diag( + dbeta_dtheta_beta_mtrx_i + ) - dbeta_dtheta_beta_mtrx_pwr2 = dbeta_dtheta_beta_mtrx @ dbeta_dtheta_beta_mtrx + dbeta_dtheta_beta_mtrx_pwr2 = ( + dbeta_dtheta_beta_mtrx @ dbeta_dtheta_beta_mtrx + ) del dbeta_dtheta_beta_mtrx @@ -2559,7 +2847,7 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # get W_beta_i - n_betas=0 + n_betas = 0 for beta_data_mtrx in beta_lst: n_betas += beta_data_mtrx.shape[1] @@ -2575,17 +2863,20 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, min_c_j = active_spc_molar_cc.min() if min_c_j <= 1e-25: - (jdx, ) = np.where(active_spc_molar_cc == min_c_j) - active_spc_molar_cc[jdx] = 1.0 # any non-zero value will do since rb_i will be zero + (jdx,) = np.where(active_spc_molar_cc == min_c_j) + active_spc_molar_cc[jdx] = ( + 1.0 # any non-zero value will do since rb_i will be zero + ) w_beta_i_vec = np.log(active_spc_molar_cc) - w_beta_i_mtrx = np.zeros((len(self.reactions), n_betas), dtype = np.float64) + w_beta_i_mtrx = np.zeros((len(self.reactions), n_betas), dtype=np.float64) - w_beta_i_column_id = \ - sum([beta_data_mtrx.shape[1] for beta_data_mtrx in beta_lst[:rxn_idx]]) + w_beta_i_column_id = sum( + [beta_data_mtrx.shape[1] for beta_data_mtrx in beta_lst[:rxn_idx]] + ) - end_plus_one = w_beta_i_column_id + w_beta_i_vec.size # end + 1 + end_plus_one = w_beta_i_column_id + w_beta_i_vec.size # end + 1 assert end_plus_one <= n_betas @@ -2599,11 +2890,12 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, d2beta_dtheta2_lst = self.__d2phi_dtheta2(theta_beta_lst, self.beta_bnds) for d2beta_dtheta2_data_mtrx in d2beta_dtheta2_lst: - - d2beta_dtheta2_mtrx_i = np.diag(d2beta_dtheta2_data_mtrx[1,:]) + d2beta_dtheta2_mtrx_i = np.diag(d2beta_dtheta2_data_mtrx[1, :]) try: - d2beta_dtheta2_mtrx = sp.linalg.block_diag(d2beta_dtheta2_mtrx, d2beta_dtheta2_mtrx_i) + d2beta_dtheta2_mtrx = sp.linalg.block_diag( + d2beta_dtheta2_mtrx, d2beta_dtheta2_mtrx_i + ) except NameError: d2beta_dtheta2_mtrx = sp.linalg.block_diag(d2beta_dtheta2_mtrx_i) @@ -2615,194 +2907,299 @@ def d2ri_theta2_mtrx_new(self, rxn_idx, spc_molar_cc_vec, # Compute product - d_theta_beta_d_theta_beta_ri_mtrx = \ - - rb_i * (dbeta_dtheta_beta_mtrx_pwr2 @ w_beta_i_T_w_beta_i_mtrx \ - + \ - d2beta_dtheta2_mtrx @ diag_w_beta_i_irow) + d_theta_beta_d_theta_beta_ri_mtrx = -rb_i * ( + dbeta_dtheta_beta_mtrx_pwr2 @ w_beta_i_T_w_beta_i_mtrx + + d2beta_dtheta2_mtrx @ diag_w_beta_i_irow + ) # ******************************************************************************************* # Assembly # General case - if theta_kf_vec is not None and theta_kb_vec is not None and \ - theta_alpha_lst is not None and theta_beta_lst is not None: - - hessian_ri_1st_row = np.hstack([d_theta_kf_d_theta_kf_ri_mtrx, - d_theta_kb_d_theta_kf_ri_mtrx, - d_theta_alpha_d_theta_kf_ri_mtrx, - d_theta_beta_d_theta_kf_ri_mtrx]) - - hessian_ri_2nd_row = np.hstack([d_theta_kb_d_theta_kf_ri_mtrx.transpose(), - d_theta_kb_d_theta_kb_ri_mtrx, - d_theta_alpha_d_theta_kb_ri_mtrx, - d_theta_beta_d_theta_kb_ri_mtrx]) - - hessian_ri_3rd_row = np.hstack([d_theta_alpha_d_theta_kf_ri_mtrx.transpose(), - d_theta_alpha_d_theta_kb_ri_mtrx.transpose(), - d_theta_alpha_d_theta_alpha_ri_mtrx, - d_theta_beta_d_theta_alpha_ri_mtrx]) - - hessian_ri_4th_row = np.hstack([d_theta_beta_d_theta_kf_ri_mtrx.transpose(), - d_theta_beta_d_theta_kb_ri_mtrx.transpose(), - d_theta_beta_d_theta_alpha_ri_mtrx.transpose(), - d_theta_beta_d_theta_beta_ri_mtrx]) - - hessian_ri = np.vstack([hessian_ri_1st_row, - hessian_ri_2nd_row, - hessian_ri_3rd_row, - hessian_ri_4th_row]) + if ( + theta_kf_vec is not None + and theta_kb_vec is not None + and theta_alpha_lst is not None + and theta_beta_lst is not None + ): + hessian_ri_1st_row = np.hstack( + [ + d_theta_kf_d_theta_kf_ri_mtrx, + d_theta_kb_d_theta_kf_ri_mtrx, + d_theta_alpha_d_theta_kf_ri_mtrx, + d_theta_beta_d_theta_kf_ri_mtrx, + ] + ) + + hessian_ri_2nd_row = np.hstack( + [ + d_theta_kb_d_theta_kf_ri_mtrx.transpose(), + d_theta_kb_d_theta_kb_ri_mtrx, + d_theta_alpha_d_theta_kb_ri_mtrx, + d_theta_beta_d_theta_kb_ri_mtrx, + ] + ) + + hessian_ri_3rd_row = np.hstack( + [ + d_theta_alpha_d_theta_kf_ri_mtrx.transpose(), + d_theta_alpha_d_theta_kb_ri_mtrx.transpose(), + d_theta_alpha_d_theta_alpha_ri_mtrx, + d_theta_beta_d_theta_alpha_ri_mtrx, + ] + ) + + hessian_ri_4th_row = np.hstack( + [ + d_theta_beta_d_theta_kf_ri_mtrx.transpose(), + d_theta_beta_d_theta_kb_ri_mtrx.transpose(), + d_theta_beta_d_theta_alpha_ri_mtrx.transpose(), + d_theta_beta_d_theta_beta_ri_mtrx, + ] + ) + + hessian_ri = np.vstack( + [ + hessian_ri_1st_row, + hessian_ri_2nd_row, + hessian_ri_3rd_row, + hessian_ri_4th_row, + ] + ) # Forward case - elif theta_kf_vec is not None and theta_alpha_lst is not None and \ - theta_kb_vec is None and theta_beta_lst is None: - - hessian_ri_1st_row = np.hstack([d_theta_kf_d_theta_kf_ri_mtrx, - d_theta_alpha_d_theta_kf_ri_mtrx]) - - hessian_ri_2nd_row = np.hstack([d_theta_alpha_d_theta_kf_ri_mtrx.transpose(), - d_theta_alpha_d_theta_alpha_ri_mtrx]) - - hessian_ri = np.vstack([hessian_ri_1st_row, - hessian_ri_2nd_row]) + elif ( + theta_kf_vec is not None + and theta_alpha_lst is not None + and theta_kb_vec is None + and theta_beta_lst is None + ): + hessian_ri_1st_row = np.hstack( + [d_theta_kf_d_theta_kf_ri_mtrx, d_theta_alpha_d_theta_kf_ri_mtrx] + ) + + hessian_ri_2nd_row = np.hstack( + [ + d_theta_alpha_d_theta_kf_ri_mtrx.transpose(), + d_theta_alpha_d_theta_alpha_ri_mtrx, + ] + ) + + hessian_ri = np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) # k's only case - elif theta_kf_vec is not None and theta_kb_vec is not None and \ - theta_alpha_lst is None and theta_beta_lst is None: - - hessian_ri_1st_row = np.hstack([d_theta_kf_d_theta_kf_ri_mtrx, - d_theta_kb_d_theta_kf_ri_mtrx]) - - hessian_ri_2nd_row = np.hstack([d_theta_kb_d_theta_kf_ri_mtrx.transpose(), - d_theta_kb_d_theta_kb_ri_mtrx]) - - hessian_ri = np.vstack([hessian_ri_1st_row, - hessian_ri_2nd_row]) + elif ( + theta_kf_vec is not None + and theta_kb_vec is not None + and theta_alpha_lst is None + and theta_beta_lst is None + ): + hessian_ri_1st_row = np.hstack( + [d_theta_kf_d_theta_kf_ri_mtrx, d_theta_kb_d_theta_kf_ri_mtrx] + ) + + hessian_ri_2nd_row = np.hstack( + [ + d_theta_kb_d_theta_kf_ri_mtrx.transpose(), + d_theta_kb_d_theta_kb_ri_mtrx, + ] + ) + + hessian_ri = np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) # kfs only case - elif theta_kf_vec is not None and theta_kb_vec is None and \ - theta_alpha_lst is None and theta_beta_lst is None: - + elif ( + theta_kf_vec is not None + and theta_kb_vec is None + and theta_alpha_lst is None + and theta_beta_lst is None + ): hessian_ri = d_theta_kf_d_theta_kf_ri_mtrx # kbs only case - elif theta_kf_vec is None and theta_kb_vec is not None and \ - theta_alpha_lst is None and theta_beta_lst is None: - + elif ( + theta_kf_vec is None + and theta_kb_vec is not None + and theta_alpha_lst is None + and theta_beta_lst is None + ): hessian_ri = d_theta_kb_d_theta_kb_ri_mtrx # k's and alphas only case - elif theta_kf_vec is not None and theta_kb_vec is not None and \ - theta_alpha_lst is not None and theta_beta_lst is None: - - hessian_ri_1st_row = np.hstack([d_theta_kf_d_theta_kf_ri_mtrx, - d_theta_kb_d_theta_kf_ri_mtrx, - d_theta_alpha_d_theta_kf_ri_mtrx]) - - hessian_ri_2nd_row = np.hstack([d_theta_kb_d_theta_kf_ri_mtrx.transpose(), - d_theta_kb_d_theta_kb_ri_mtrx, - d_theta_alpha_d_theta_kb_ri_mtrx]) - - hessian_ri_3rd_row = np.hstack([d_theta_alpha_d_theta_kf_ri_mtrx.transpose(), - d_theta_alpha_d_theta_kb_ri_mtrx.transpose(), - d_theta_alpha_d_theta_alpha_ri_mtrx]) - - hessian_ri = np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row, - hessian_ri_3rd_row]) + elif ( + theta_kf_vec is not None + and theta_kb_vec is not None + and theta_alpha_lst is not None + and theta_beta_lst is None + ): + hessian_ri_1st_row = np.hstack( + [ + d_theta_kf_d_theta_kf_ri_mtrx, + d_theta_kb_d_theta_kf_ri_mtrx, + d_theta_alpha_d_theta_kf_ri_mtrx, + ] + ) + + hessian_ri_2nd_row = np.hstack( + [ + d_theta_kb_d_theta_kf_ri_mtrx.transpose(), + d_theta_kb_d_theta_kb_ri_mtrx, + d_theta_alpha_d_theta_kb_ri_mtrx, + ] + ) + + hessian_ri_3rd_row = np.hstack( + [ + d_theta_alpha_d_theta_kf_ri_mtrx.transpose(), + d_theta_alpha_d_theta_kb_ri_mtrx.transpose(), + d_theta_alpha_d_theta_alpha_ri_mtrx, + ] + ) + + hessian_ri = np.vstack( + [hessian_ri_1st_row, hessian_ri_2nd_row, hessian_ri_3rd_row] + ) # alphas only case - elif theta_kf_vec is None and theta_kb_vec is None and \ - theta_alpha_lst is not None and theta_beta_lst is None: - + elif ( + theta_kf_vec is None + and theta_kb_vec is None + and theta_alpha_lst is not None + and theta_beta_lst is None + ): hessian_ri = d_theta_alpha_d_theta_alpha_ri_mtrx # betas only case - elif theta_kf_vec is None and theta_kb_vec is None and \ - theta_alpha_lst is None and theta_beta_lst is not None: - + elif ( + theta_kf_vec is None + and theta_kb_vec is None + and theta_alpha_lst is None + and theta_beta_lst is not None + ): hessian_ri = d_theta_beta_d_theta_beta_ri_mtrx # alphas and betas only case - elif theta_kf_vec is None and theta_kb_vec is None and \ - theta_alpha_lst is not None and theta_beta_lst is not None: - - hessian_ri_1st_row = np.hstack([d_theta_alpha_d_theta_alpha_ri_mtrx, - d_theta_beta_d_theta_alpha_ri_mtrx]) - - hessian_ri_2nd_row = np.hstack([d_theta_beta_d_theta_alpha_ri_mtrx.transpose(), - d_theta_beta_d_theta_beta_ri_mtrx]) - - hessian_ri = np.vstack([hessian_ri_1st_row, - hessian_ri_2nd_row]) + elif ( + theta_kf_vec is None + and theta_kb_vec is None + and theta_alpha_lst is not None + and theta_beta_lst is not None + ): + hessian_ri_1st_row = np.hstack( + [ + d_theta_alpha_d_theta_alpha_ri_mtrx, + d_theta_beta_d_theta_alpha_ri_mtrx, + ] + ) + + hessian_ri_2nd_row = np.hstack( + [ + d_theta_beta_d_theta_alpha_ri_mtrx.transpose(), + d_theta_beta_d_theta_beta_ri_mtrx, + ] + ) + + hessian_ri = np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) # kfs and betas only case - elif theta_kf_vec is not None and theta_kb_vec is None and \ - theta_alpha_lst is None and theta_beta_lst is not None: - - hessian_ri_1st_row = np.hstack([d_theta_kf_d_theta_kf_ri_mtrx, - d_theta_beta_d_theta_kf_ri_mtrx]) - - hessian_ri_2nd_row = np.hstack([d_theta_beta_d_theta_kf_ri_mtrx.transpose(), - d_theta_beta_d_theta_beta_ri_mtrx]) - - hessian_ri=np.vstack([hessian_ri_1st_row, - hessian_ri_2nd_row]) + elif ( + theta_kf_vec is not None + and theta_kb_vec is None + and theta_alpha_lst is None + and theta_beta_lst is not None + ): + hessian_ri_1st_row = np.hstack( + [d_theta_kf_d_theta_kf_ri_mtrx, d_theta_beta_d_theta_kf_ri_mtrx] + ) + + hessian_ri_2nd_row = np.hstack( + [ + d_theta_beta_d_theta_kf_ri_mtrx.transpose(), + d_theta_beta_d_theta_beta_ri_mtrx, + ] + ) + + hessian_ri = np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) # kbs and alphas only case - elif theta_kf_vec is None and theta_kb_vec is not None and \ - theta_alpha_lst is not None and theta_beta_lst is None: - - hessian_ri_1st_row = np.hstack([d_theta_kb_d_theta_kb_ri_mtrx, - d_theta_alpha_d_theta_kb_ri_mtrx]) - - hessian_ri_2nd_row = np.hstack([d_theta_alpha_d_theta_kb_ri_mtrx.transpose(), - d_theta_alpha_d_theta_alpha_ri_mtrx]) - - hessian_ri = np.vstack([hessian_ri_1st_row, - hessian_ri_2nd_row]) + elif ( + theta_kf_vec is None + and theta_kb_vec is not None + and theta_alpha_lst is not None + and theta_beta_lst is None + ): + hessian_ri_1st_row = np.hstack( + [d_theta_kb_d_theta_kb_ri_mtrx, d_theta_alpha_d_theta_kb_ri_mtrx] + ) + + hessian_ri_2nd_row = np.hstack( + [ + d_theta_alpha_d_theta_kb_ri_mtrx.transpose(), + d_theta_alpha_d_theta_alpha_ri_mtrx, + ] + ) + + hessian_ri = np.vstack([hessian_ri_1st_row, hessian_ri_2nd_row]) # kfs and kbs and betas only case - elif theta_kf_vec is not None and theta_kb_vec is not None and \ - theta_alpha_lst is None and theta_beta_lst is not None: - - hessian_ri_1st_row = np.hstack([d_theta_kf_d_theta_kf_ri_mtrx, - d_theta_kb_d_theta_kf_ri_mtrx, - d_theta_beta_d_theta_kf_ri_mtrx]) - - hessian_ri_2nd_row = np.hstack([d_theta_kb_d_theta_kf_ri_mtrx.transpose(), - d_theta_kb_d_theta_kb_ri_mtrx, - d_theta_beta_d_theta_kb_ri_mtrx]) - - hessian_ri_3rd_row = np.hstack([d_theta_beta_d_theta_kf_ri_mtrx.transpose(), - d_theta_beta_d_theta_kb_ri_mtrx.transpose(), - d_theta_beta_d_theta_beta_ri_mtrx]) - - hessian_ri = np.vstack([hessian_ri_1st_row, - hessian_ri_2nd_row, - hessian_ri_3rd_row]) + elif ( + theta_kf_vec is not None + and theta_kb_vec is not None + and theta_alpha_lst is None + and theta_beta_lst is not None + ): + hessian_ri_1st_row = np.hstack( + [ + d_theta_kf_d_theta_kf_ri_mtrx, + d_theta_kb_d_theta_kf_ri_mtrx, + d_theta_beta_d_theta_kf_ri_mtrx, + ] + ) + + hessian_ri_2nd_row = np.hstack( + [ + d_theta_kb_d_theta_kf_ri_mtrx.transpose(), + d_theta_kb_d_theta_kb_ri_mtrx, + d_theta_beta_d_theta_kb_ri_mtrx, + ] + ) + + hessian_ri_3rd_row = np.hstack( + [ + d_theta_beta_d_theta_kf_ri_mtrx.transpose(), + d_theta_beta_d_theta_kb_ri_mtrx.transpose(), + d_theta_beta_d_theta_beta_ri_mtrx, + ] + ) + + hessian_ri = np.vstack( + [hessian_ri_1st_row, hessian_ri_2nd_row, hessian_ri_3rd_row] + ) else: - assert False, 'Hessian ri case not implemented.' + assert False, "Hessian ri case not implemented." return hessian_ri def __get_kf(self): - '''Utility for returning a packed kf vector. + """Utility for returning a packed kf vector. Should this return the stoichiometric coefficients in case there is no data in `self.data`? Returns ------- kf_vec: numpy.ndarray - ''' + """ - kf_vec = np.zeros(len(self.reactions), dtype = np.float64) + kf_vec = np.zeros(len(self.reactions), dtype=np.float64) for idx, rxn_data in enumerate(self.data): - kf_vec[idx] = rxn_data['k_f'] + kf_vec[idx] = rxn_data["k_f"] return kf_vec + def __set_kf(self, kf_vec): - '''Utility for setting kf from packed vectors. + """Utility for setting kf from packed vectors. Parameters ---------- @@ -2810,33 +3207,35 @@ def __set_kf(self, kf_vec): Returns ------- - ''' + """ assert isinstance(kf_vec, numpy.ndarray) assert kf_vec.size == len(self.reactions) for idx, rxn_data in enumerate(self.data): - rxn_data['k_f'] = kf_vec[idx] + rxn_data["k_f"] = kf_vec[idx] + kf = property(__get_kf, __set_kf, None, None) def __get_kb(self): - '''Utility for returning a packed kb vector. + """Utility for returning a packed kb vector. Should this return the stoichiometric coefficients in case there is no data in `self.data`? Returns ------- kb_vec: numpy.ndarray - ''' + """ - kb_vec= np.zeros(len(self.reactions), dtype = np.float64) + kb_vec = np.zeros(len(self.reactions), dtype=np.float64) for idx, rxn_data in enumerate(self.data): - kb_vec[idx]=rxn_data['k_b'] + kb_vec[idx] = rxn_data["k_b"] return kb_vec + def __set_kb(self, kb_vec): - '''Utility for setting kb from packed vectors. + """Utility for setting kb from packed vectors. Parameters ---------- @@ -2844,28 +3243,30 @@ def __set_kb(self, kb_vec): Returns ------- - ''' + """ assert isinstance(kb_vec, numpy.ndarray) assert kb_vec.size == len(self.reactions) for idx, rxn_data in enumerate(self.data): - rxn_data['k_b']=kb_vec[idx] + rxn_data["k_b"] = kb_vec[idx] + kb = property(__get_kb, __set_kb, None, None) def __get_ks(self): - '''Utility for returning packed kf and kb into vectors. + """Utility for returning packed kf and kb into vectors. Should this return the stoichiometric coefficients in case there is no data in `self.data`? Returns ------- (kf_vec, kb_vec): tuple(numpy.ndarray, numpy.ndarray) - ''' + """ return (self.__get_kf(), self.__get_kb()) + def __set_ks(self, kf_kb_pair): - '''Utility for setting kf and kb from packed vectors. + """Utility for setting kf and kb from packed vectors. Parameters ---------- @@ -2874,7 +3275,7 @@ def __set_ks(self, kf_kb_pair): Returns ------- - ''' + """ assert isinstance(kf_kb_pair, tuple) assert len(kf_kb_pair) == 2 @@ -2883,10 +3284,11 @@ def __set_ks(self, kf_kb_pair): self.__set_kf(kf_kb_pair[0]) if kf_kb_pair[1] is not None: self.__set_kb(kf_kb_pair[1]) + ks = property(__get_ks, __set_ks, None, None) def __get_alpha(self): - '''Utility for packing alpha into a list of matrices. + """Utility for packing alpha into a list of matrices. The return from this method is a list of compressed unstructured data since each reaction typically has a different number of active species, hence different number of associated @@ -2897,17 +3299,16 @@ def __get_alpha(self): Returns ------- alpha_lst: list(numpy.ndarray) - ''' + """ alpha_lst = list() # list of matrices - for (idx, rxn_data) in enumerate(self.data): - - (reactants_ids, ) = np.where(self.stoic_mtrx[idx, :] < 0) + for idx, rxn_data in enumerate(self.data): + (reactants_ids,) = np.where(self.stoic_mtrx[idx, :] < 0) # reactants_ids = reactants_ids_lst[idx] - if 'alpha' in rxn_data.keys(): - alpha_dict = rxn_data['alpha'] + if "alpha" in rxn_data.keys(): + alpha_dict = rxn_data["alpha"] exponents = list() active_reactants_ids = list() for j in reactants_ids: @@ -2917,7 +3318,9 @@ def __get_alpha(self): active_reactants_ids.append(j) exponents.append(alpha_dict[spc_name]) - reactants_ids_alphas = np.array((active_reactants_ids, exponents)) # 2-row matrix + reactants_ids_alphas = np.array( + (active_reactants_ids, exponents) + ) # 2-row matrix else: assert False @@ -2926,8 +3329,9 @@ def __get_alpha(self): alpha_lst.append(reactants_ids_alphas) return alpha_lst + def __set_alpha(self, alpha_lst): - '''Utility for setting alpha from packed matrices. + """Utility for setting alpha from packed matrices. The alpha list of matrices with values for the exponents and corresponding active species ids. Note that this will change the internal data of the object including inactive @@ -2938,15 +3342,14 @@ def __set_alpha(self, alpha_lst): alpha: list(numpy.ndarray) If any element of the list is None, the corresponding data is not updated. - ''' + """ if alpha_lst is not None: assert isinstance(alpha_lst, list) for idx, rxn_data in enumerate(self.data): - - if 'alpha' in rxn_data.keys(): - alpha_dict = rxn_data['alpha'] + if "alpha" in rxn_data.keys(): + alpha_dict = rxn_data["alpha"] alpha_mtrx = alpha_lst[idx] reactants_ids = alpha_mtrx[0, :].astype(int) exponents = alpha_mtrx[1, :] @@ -2956,10 +3359,11 @@ def __set_alpha(self, alpha_lst): else: assert False exponents = -self.stoic_mtrx[idx, reactants_ids] + alpha = property(__get_alpha, __set_alpha, None, None) def __get_beta(self): - '''Utility for packing beta exponents into a list of matrices. + """Utility for packing beta exponents into a list of matrices. The return from this method is a list of compressed unstructured data since each reaction typically has a different number of active species, hence different number of associated @@ -2970,16 +3374,15 @@ def __get_beta(self): Returns ------- beta_lst: list(numpy.ndarray) - ''' - - beta_lst = list() # list of matrices + """ - for (idx, rxn_data) in enumerate(self.data): + beta_lst = list() # list of matrices - (products_ids, ) = np.where(self.stoic_mtrx[idx, :] > 0) + for idx, rxn_data in enumerate(self.data): + (products_ids,) = np.where(self.stoic_mtrx[idx, :] > 0) - if 'beta' in rxn_data.keys(): - beta_dict = rxn_data['beta'] + if "beta" in rxn_data.keys(): + beta_dict = rxn_data["beta"] exponents = list() active_products_ids = list() for j in products_ids: @@ -2989,7 +3392,9 @@ def __get_beta(self): active_products_ids.append(j) exponents.append(beta_dict[spc_name]) - products_ids_betas = np.array((active_products_ids, exponents)) # 2-row matrix + products_ids_betas = np.array( + (active_products_ids, exponents) + ) # 2-row matrix else: assert False @@ -2998,8 +3403,9 @@ def __get_beta(self): beta_lst.append(products_ids_betas) return beta_lst + def __set_beta(self, beta_lst): - '''Utility for setting beta from packed matrices. + """Utility for setting beta from packed matrices. The alpha list of matrices with values for the exponents and corresponding active species ids. Note that this will change the internal data of the object including inactive @@ -3010,15 +3416,14 @@ def __set_beta(self, beta_lst): beta: list(numpy.ndarray) If any element of the list is None, the corresponding data is not updated. - ''' + """ if beta_lst is not None: assert isinstance(beta_lst, list) for idx, rxn_data in enumerate(self.data): - - if 'beta' in rxn_data.keys(): - beta_dict = rxn_data['beta'] + if "beta" in rxn_data.keys(): + beta_dict = rxn_data["beta"] beta_mtrx = beta_lst[idx] products_ids = beta_mtrx[0, :].astype(int) exponents = beta_mtrx[1, :] @@ -3028,10 +3433,11 @@ def __set_beta(self, beta_lst): else: assert False exponents = self.stoic_mtrx[idx, products_ids] + beta = property(__get_beta, __set_beta, None, None) def __get_power_law_exponents(self): - '''Utility for packing alpha and beta exponents into a list of vectors. + """Utility for packing alpha and beta exponents into a list of vectors. The return from this method is a pair of unstructured data since each reaction typically has a different number of active species, hence different number of associated power-law exponents. @@ -3041,18 +3447,17 @@ def __get_power_law_exponents(self): Returns ------- (alpha_lst, beta_lst): tuple(list(numpy.ndarray), list(numpy.ndarray)) - ''' + """ alpha_lst = list() # list of matrices - beta_lst = list() # list of matrices - - for (idx, rxn_data) in enumerate(self.data): + beta_lst = list() # list of matrices - (reactants_ids, ) = np.where(self.stoic_mtrx[idx, :] < 0) + for idx, rxn_data in enumerate(self.data): + (reactants_ids,) = np.where(self.stoic_mtrx[idx, :] < 0) # reactants_ids = reactants_ids_lst[idx] - if 'alpha' in rxn_data.keys(): - alpha_dict = rxn_data['alpha'] + if "alpha" in rxn_data.keys(): + alpha_dict = rxn_data["alpha"] exponents = list() active_reactants_ids = list() for j in reactants_ids: @@ -3062,7 +3467,9 @@ def __get_power_law_exponents(self): active_reactants_ids.append(j) exponents.append(alpha_dict[spc_name]) - reactants_ids_alphas = np.array((active_reactants_ids, exponents)) # 2-row matrix + reactants_ids_alphas = np.array( + (active_reactants_ids, exponents) + ) # 2-row matrix else: assert False @@ -3070,10 +3477,10 @@ def __get_power_law_exponents(self): alpha_lst.append(reactants_ids_alphas) - (products_ids, ) = np.where(self.stoic_mtrx[idx, :] > 0) + (products_ids,) = np.where(self.stoic_mtrx[idx, :] > 0) - if 'beta' in rxn_data.keys(): - beta_dict = rxn_data['beta'] + if "beta" in rxn_data.keys(): + beta_dict = rxn_data["beta"] exponents = list() active_products_ids = list() for j in products_ids: @@ -3083,7 +3490,9 @@ def __get_power_law_exponents(self): active_products_ids.append(j) exponents.append(beta_dict[spc_name]) - products_ids_betas = np.array((active_products_ids, exponents)) # 2-row matrix + products_ids_betas = np.array( + (active_products_ids, exponents) + ) # 2-row matrix else: assert False @@ -3092,8 +3501,9 @@ def __get_power_law_exponents(self): beta_lst.append(products_ids_betas) return (alpha_lst, beta_lst) + def __set_power_law_exponents(self, alpha_beta_pair): - '''Utility for setting alpha and beta from packed vectors. + """Utility for setting alpha and beta from packed vectors. The alpha and vector lists of matrices with values for the exponents and corresponding active species ids. Note that this will change the internal data of the object including inactive @@ -3104,19 +3514,18 @@ def __set_power_law_exponents(self, alpha_beta_pair): alpha_beta_pair: tuple(list(numpy.ndarray), list(numpy.ndarray)) If any element of the tuple is None, the corresponding data is not updated. - ''' + """ assert isinstance(alpha_beta_pair, tuple) - #assert len(alpha_beta_pair) == 2 + # assert len(alpha_beta_pair) == 2 if alpha_beta_pair[0] is not None: assert isinstance(alpha_beta_pair[0], list) - alpha_lst=alpha_beta_pair[0] + alpha_lst = alpha_beta_pair[0] for idx, rxn_data in enumerate(self.data): - - if 'alpha' in rxn_data.keys(): - alpha_dict = rxn_data['alpha'] + if "alpha" in rxn_data.keys(): + alpha_dict = rxn_data["alpha"] alpha_mtrx = alpha_lst[idx] reactants_ids = alpha_mtrx[0, :].astype(int) exponents = alpha_mtrx[1, :] @@ -3132,9 +3541,8 @@ def __set_power_law_exponents(self, alpha_beta_pair): beta_lst = alpha_beta_pair[1] for idx, rxn_data in enumerate(self.data): - - if 'beta' in rxn_data.keys(): - beta_dict = rxn_data['beta'] + if "beta" in rxn_data.keys(): + beta_dict = rxn_data["beta"] beta_mtrx = beta_lst[idx] products_ids = beta_mtrx[0, :].astype(int) exponents = beta_mtrx[1, :] @@ -3144,48 +3552,53 @@ def __set_power_law_exponents(self, alpha_beta_pair): else: assert False exponents = self.stoic_mtrx[idx, products_ids] - power_law_exponents = property(__get_power_law_exponents, __set_power_law_exponents, None, None) - def full_rank_sub_mechanisms(self, n_sub_mec = 1000): - '''Construct sub-mechanisms with full-rank stoichiometric matrix. + power_law_exponents = property( + __get_power_law_exponents, __set_power_law_exponents, None, None + ) + + def full_rank_sub_mechanisms(self, n_sub_mec=1000): + """Construct sub-mechanisms with full-rank stoichiometric matrix. Returns ------- sub_mechanisms: list([ReactionMechanism, gidxs, score]) - ''' + """ - s_rank=np.linalg.matrix_rank(self.stoic_mtrx) + s_rank = np.linalg.matrix_rank(self.stoic_mtrx) if s_rank == min(self.stoic_mtrx.shape): return self - m_reactions=self.stoic_mtrx.shape[0] + m_reactions = self.stoic_mtrx.shape[0] - n_mechanisms=math.factorial(m_reactions) /\ - math.factorial(m_reactions - s_rank) /\ - math.factorial(s_rank) + n_mechanisms = ( + math.factorial(m_reactions) + / math.factorial(m_reactions - s_rank) + / math.factorial(s_rank) + ) # print('# of all possible sub_mechanisms = %i'%n_mechanisms) - tmp=combinations(range(m_reactions), s_rank) - reaction_sets=[i for i in tmp] + tmp = combinations(range(m_reactions), s_rank) + reaction_sets = [i for i in tmp] random.shuffle(reaction_sets) # this may be time consuming - sub_mechanisms=list() # list of list + sub_mechanisms = list() # list of list for idxs in reaction_sets: + stoic_mtrx = self.stoic_mtrx[idxs, :] - stoic_mtrx=self.stoic_mtrx[idxs, :] - - rank=np.linalg.matrix_rank(stoic_mtrx) + rank = np.linalg.matrix_rank(stoic_mtrx) if rank == s_rank: - - mechanism=list() + mechanism = list() for idx in idxs: mechanism.append(self.__original_mechanism[idx]) - rxn_mech = ReactionMechanism(mechanism = mechanism, order_species =True, reparam=self.reparam) + rxn_mech = ReactionMechanism( + mechanism=mechanism, order_species=True, reparam=self.reparam + ) assert np.linalg.matrix_rank(rxn_mech.stoic_mtrx) == s_rank @@ -3195,31 +3608,34 @@ def full_rank_sub_mechanisms(self, n_sub_mec = 1000): break # Count number of times a global reaction appear in a sub-mechanism - reactions_hits=np.zeros(m_reactions) + reactions_hits = np.zeros(m_reactions) for sm in sub_mechanisms: - gidxs=list(sm[1]) + gidxs = list(sm[1]) reactions_hits[gidxs] += 1 # Score the global reactions - sub_mech_reactions_score=list() + sub_mech_reactions_score = list() for subm in sub_mechanisms: - score=0 + score = 0 for gid in subm[1]: score += reactions_hits[gid] sub_mech_reactions_score.append(score) - sub_mech_reactions_score=np.array(sub_mech_reactions_score) + sub_mech_reactions_score = np.array(sub_mech_reactions_score) sub_mech_reactions_score /= sub_mech_reactions_score.max() sub_mech_reactions_score *= 10.0 - results=sorted(zip(sub_mechanisms, sub_mech_reactions_score), - key=lambda entry: entry[1], reverse=True ) + results = sorted( + zip(sub_mechanisms, sub_mech_reactions_score), + key=lambda entry: entry[1], + reverse=True, + ) - sub_mechanisms = [a for (a,b) in results] - sub_mech_reactions_score = [b for (a,b) in results] + sub_mechanisms = [a for (a, b) in results] + sub_mech_reactions_score = [b for (a, b) in results] # Encode score in to sub_mech_reactions mech. list of list - for (sr, score) in zip(sub_mechanisms, sub_mech_reactions_score): + for sr, score in zip(sub_mechanisms, sub_mech_reactions_score): sr += [score] # max_score = max( [sm[3] for sm in sub_mechanisms] ) @@ -3227,23 +3643,21 @@ def full_rank_sub_mechanisms(self, n_sub_mec = 1000): return sub_mechanisms def print_data(self): - '''Helper to print the reaction data line by line. - ''' + """Helper to print the reaction data line by line.""" for idx, data in enumerate(self.data): print(self.reactions[idx]) - print(data,'\n') + print(data, "\n") def print_species(self): - '''Helper to print species data line by line. - ''' + """Helper to print species data line by line.""" for spc in self.species: print(spc) - print('') + print("") def md_print(self): - '''Markdown cell printout of LaTex reactions and species. + """Markdown cell printout of LaTex reactions and species. Use with Jupyter Notebooks in a code cell. @@ -3262,18 +3676,19 @@ def md_print(self): rxn_mech = ReactionMechanism(file_name='some_mech.txt') rxn_mech.md_print() - ''' + """ from IPython.display import Markdown, display - tmp = self.latex_species.replace(',',' \\quad ') - string = '%i **Species:** \n $%s$'%(len(self.species_names), tmp) + + tmp = self.latex_species.replace(",", " \\quad ") + string = "%i **Species:** \n $%s$" % (len(self.species_names), tmp) display(Markdown(string)) - string = '%i **Reactions:** \n %s'%(len(self.reactions), self.latex_rxn) + string = "%i **Reactions:** \n %s" % (len(self.reactions), self.latex_rxn) display(Markdown(string)) def __latex(self): - '''Internal helper for LaTeX typesetting. + """Internal helper for LaTeX typesetting. See attributes description and usage with the Python print() function. notebook. @@ -3284,98 +3699,111 @@ def __latex(self): rxn_mech = ReactionMechanism(file_name='some_mech.txt') print(rxn_mech.species_str) print(rxn_mech.rxn_str) - ''' + """ # Latex species species_str = str() for spc in self.species[:-1]: - species_str += spc.latex_name + ', ' + species_str += spc.latex_name + ", " species_str += self.species[-1].latex_name # Latex reactions into align environment # No header - #rxn_str = self.header + '\n' - #rxn_str += '\\begin{align*} \n' - rxn_str = '\\begin{align*} \n' - for idx,row in enumerate(self.stoic_mtrx): - - (reactants_ids, ) = np.where(row < 0) + # rxn_str = self.header + '\n' + # rxn_str += '\\begin{align*} \n' + rxn_str = "\\begin{align*} \n" + for idx, row in enumerate(self.stoic_mtrx): + (reactants_ids,) = np.where(row < 0) for j in reactants_ids[:-1]: - coeff = abs(self.stoic_mtrx[idx,j]) + coeff = abs(self.stoic_mtrx[idx, j]) if coeff != 1: - rxn_str += str(coeff) + '\\,' + self.species[j].latex_name + r'\ + \ ' + rxn_str += ( + str(coeff) + "\\," + self.species[j].latex_name + r"\ + \ " + ) else: - rxn_str += self.species[j].latex_name + r'\ + \ ' + rxn_str += self.species[j].latex_name + r"\ + \ " j = reactants_ids[-1] - coeff = abs(self.stoic_mtrx[idx,j]) + coeff = abs(self.stoic_mtrx[idx, j]) if coeff != 1: - rxn_str += str(coeff) + '\\,' + self.species[j].latex_name + rxn_str += str(coeff) + "\\," + self.species[j].latex_name else: rxn_str += self.species[j].latex_name - if self.reaction_direction_symbol[idx] == '->': - rxn_str += r'\ &\longrightarrow \ ' - elif self.reaction_direction_symbol[idx] == '<->': - rxn_str += r'\ &\longleftrightarrow \ ' - elif self.reaction_direction_symbol[idx] == '<=>': - rxn_str += r'\ &\longleftrightarrow \ ' - elif self.reaction_direction_symbol[idx] == '<-': - rxn_str += r'\ &\longleftarrow \ ' + if self.reaction_direction_symbol[idx] == "->": + rxn_str += r"\ &\longrightarrow \ " + elif self.reaction_direction_symbol[idx] == "<->": + rxn_str += r"\ &\longleftrightarrow \ " + elif self.reaction_direction_symbol[idx] == "<=>": + rxn_str += r"\ &\longleftrightarrow \ " + elif self.reaction_direction_symbol[idx] == "<-": + rxn_str += r"\ &\longleftarrow \ " else: - assert False, 'Unknown reaction direction.' + assert False, "Unknown reaction direction." - (products_ids, ) = np.where(row > 0) + (products_ids,) = np.where(row > 0) for j in products_ids[:-1]: - coeff = abs(self.stoic_mtrx[idx,j]) + coeff = abs(self.stoic_mtrx[idx, j]) if coeff != 1: - rxn_str += str(coeff) + '\\,' + self.species[j].latex_name + r'\ + \ ' + rxn_str += ( + str(coeff) + "\\," + self.species[j].latex_name + r"\ + \ " + ) else: - rxn_str += self.species[j].latex_name + r'\ + \ ' + rxn_str += self.species[j].latex_name + r"\ + \ " j = products_ids[-1] - coeff = abs(self.stoic_mtrx[idx,j]) + coeff = abs(self.stoic_mtrx[idx, j]) if coeff != 1: - rxn_str += str(coeff) + '\\,' + self.species[j].latex_name + '\\\\ \n' + rxn_str += str(coeff) + "\\," + self.species[j].latex_name + "\\\\ \n" else: - rxn_str += self.species[j].latex_name + '\\\\ \n' + rxn_str += self.species[j].latex_name + "\\\\ \n" - rxn_str += '\\end{align*} \n' + rxn_str += "\\end{align*} \n" return (species_str, rxn_str) def __str__(self): - s = '\n\t **ReactionMechanism()**:' + \ - '\n\t header: %s;' + \ - '\n\t reactions: %s;' + \ - '\n\t data: %s;' + \ - '\n\t species_names: %s;' + \ - '\n\t species: %s' + \ - '\n\t max mass balance residual: %s' - return s % (self.header, - self.reactions, - self.data, - self.species_names, - self.species, - str(self.max_mass_balance_residual())) + s = ( + "\n\t **ReactionMechanism()**:" + + "\n\t header: %s;" + + "\n\t reactions: %s;" + + "\n\t data: %s;" + + "\n\t species_names: %s;" + + "\n\t species: %s" + + "\n\t max mass balance residual: %s" + ) + return s % ( + self.header, + self.reactions, + self.data, + self.species_names, + self.species, + str(self.max_mass_balance_residual()), + ) + def __repr__(self): - s = '\n\t **ReactionMechanism()**:' + \ - '\n\t header: %s;' + \ - '\n\t reactions: %s;' + \ - '\n\t data: %s;' + \ - '\n\t species_names: %s;' + \ - '\n\t species: %s;' + \ - '\n\t max mass balance residual: %s' - return s % (self.header, - self.reactions, - self.data, - self.species_names, - self.species, - str(self.max_mass_balance_residual())) + s = ( + "\n\t **ReactionMechanism()**:" + + "\n\t header: %s;" + + "\n\t reactions: %s;" + + "\n\t data: %s;" + + "\n\t species_names: %s;" + + "\n\t species: %s;" + + "\n\t max mass balance residual: %s" + ) + return s % ( + self.header, + self.reactions, + self.data, + self.species_names, + self.species, + str(self.max_mass_balance_residual()), + ) + def print_reaction_sub_mechanisms(sub_mechanisms, mode=None, n_sub_mech=None): - ''' + """ Nice printout of a scored reaction sub-mechanism list Once the sub-mechanisms have been computed this function makes a printout. @@ -3396,18 +3824,18 @@ def print_reaction_sub_mechanisms(sub_mechanisms, mode=None, n_sub_mech=None): Examples -------- - ''' + """ assert mode is None or n_sub_mech is None - assert mode =='top' or mode =='all' or mode==None + assert mode == "top" or mode == "all" or mode == None assert isinstance(n_sub_mech, int) or n_sub_mech is None if mode is None and n_sub_mech is None: - mode = 'all' + mode = "all" if n_sub_mech is None: - if mode == 'all': + if mode == "all": n_sub_mech = len(sub_mechanisms) - elif mode == 'top': + elif mode == "top": scores = [sm[2] for sm in sub_mechanisms] max_score = max(scores) tmp = list() @@ -3416,10 +3844,10 @@ def print_reaction_sub_mechanisms(sub_mechanisms, mode=None, n_sub_mech=None): tmp.append(s) n_sub_mech = len(tmp) else: - assert False, 'illegal mode %r'%mode + assert False, "illegal mode %r" % mode for idx, rsm in enumerate(sub_mechanisms[:n_sub_mech]): - print('Full-Rank Reaction Sub Mechanism: %s (score %4.2f)'%(idx, rsm[2])) - print('Species = ',rsm[0].species_names) - for (r,data,gidx) in zip(rsm[0].reactions, rsm[0].data, rsm[1]): - print('r%s'%gidx, r,' ', data['info']) + print("Full-Rank Reaction Sub Mechanism: %s (score %4.2f)" % (idx, rsm[2])) + print("Species = ", rsm[0].species_names) + for r, data, gidx in zip(rsm[0].reactions, rsm[0].data, rsm[1]): + print("r%s" % gidx, r, " ", data["info"]) diff --git a/cortix/support/nuclear/actor.py b/cortix/support/nuclear/actor.py index 5e7d0f7d..a224259a 100644 --- a/cortix/support/nuclear/actor.py +++ b/cortix/support/nuclear/actor.py @@ -8,85 +8,87 @@ # # Licensed under the University of Massachusetts Lowell LICENSE: # https://github.com/dpploy/cortix/blob/master/LICENSE.txt -''' +""" This is a simple way to hide the name of species of interest in a simulation. The user would modify and copy this class into the Cortix module of interest and keep it private. Author: Valmor de Almeida dealmeidav@ornl.gov; vfda Sat Aug 15 13:41:12 EDT 2015 -''' -#********************************************************************************* +""" + +# ********************************************************************************* import os import sys -#********************************************************************************* +# ********************************************************************************* -class Actor(): - ''' - See atoms list in Specie. - ''' -#********************************************************************************* -# Construction -#********************************************************************************* +class Actor: + """ + See atoms list in Specie. + """ - def __init__( self, name ): + # ********************************************************************************* + # Construction + # ********************************************************************************* + def __init__(self, name): assert isinstance(name, str) # ** this is the section to be modified by the user of this class ** self.__name_atoms_formula = { - 'water16' : [['2*H-1','O-16'],'H2O'], - 'spc(v)' : [['2*O-16'],'O2'], - 'spc1(v)' : [['O-16'],'O'], - 'spc2(v)' : [['Xe-136'],'Xe'], - 'spc3(v)' : [['I-127'],'I'], - } + "water16": [["2*H-1", "O-16"], "H2O"], + "spc(v)": [["2*O-16"], "O2"], + "spc1(v)": [["O-16"], "O"], + "spc2(v)": [["Xe-136"], "Xe"], + "spc3(v)": [["I-127"], "I"], + } # ** do not modify beyond this point ** - assert name in self.__name_atoms_formula.keys(), 'name %r not valid.' % name + assert name in self.__name_atoms_formula.keys(), "name %r not valid." % name self.__atoms = self.__name_atoms_formula[name][0] formula = self.__name_atoms_formula[name][1] - assert isinstance(formula, str), 'formula %r not valid.' % formula + assert isinstance(formula, str), "formula %r not valid." % formula self.__formula = formula return -#********************************************************************************* -# Public member functions -#********************************************************************************* + # ********************************************************************************* + # Public member functions + # ********************************************************************************* def __get_atoms(self): - - ''' + """ Returns an ordered list of the number and type of all atoms that make up the actor. For example, ['2*H1', 'O16']. Returns ------- atoms: list(str) - ''' + """ return self.__atoms + atoms = property(__get_atoms, None, None, None) def __get_formula(self): - - ''' + """ Returns the formula of the chemical in question. For example, 'H2O. Returns ------- formula: str - ''' + """ return self.__formula + formula = property(__get_formula, None, None, None) -#********************************************************************************* + +# ********************************************************************************* # Private helper functions (internal use: __) -#********************************************************************************* +# ********************************************************************************* -#======================= end class Actor ========================================= +# ======================= end class Actor ========================================= diff --git a/cortix/support/nuclear/fuel_bucket.py b/cortix/support/nuclear/fuel_bucket.py index fce1a58d..d1152fa8 100644 --- a/cortix/support/nuclear/fuel_bucket.py +++ b/cortix/support/nuclear/fuel_bucket.py @@ -8,7 +8,7 @@ # # Licensed under the University of Massachusetts Lowell LICENSE: # https://github.com/dpploy/cortix/blob/master/LICENSE.txt -''' +""" This FuelBucket class is a container for usage with other plant-level process modules. It is meant to represent a fuel bucket of a metal fuel reactor. ---------- @@ -18,26 +18,24 @@ responsible to make the "history" of the phases consistent. See Phase() info. Author: Valmor de Almeida dealmeidav@ornl.gov; vfda -''' -#********************************************************************************* +""" + +# ********************************************************************************* import os import sys import math import pandas from copy import deepcopy -#********************************************************************************* - -class FuelBucket(): +# ********************************************************************************* -#********************************************************************************* -# Construction -#********************************************************************************* - def __init__(self, - specs=pandas.DataFrame() - ): +class FuelBucket: + # ********************************************************************************* + # Construction + # ********************************************************************************* - assert isinstance(specs, pandas.DataFrame), 'oops not pandas table.' + def __init__(self, specs=pandas.DataFrame()): + assert isinstance(specs, pandas.DataFrame), "oops not pandas table." self.__specs = specs @@ -48,144 +46,146 @@ def __init__(self, return -#********************************************************************************** -# Public Member Functions -#********************************************************************************** + # ********************************************************************************** + # Public Member Functions + # ********************************************************************************** # Start: Pre-irradiation information def get_name(self): - - ''' + """ Returns the name of the fuel bucket. Returns ------- name: str - ''' + """ + + return self.__specs.loc["Name", 1] - return self.__specs.loc['Name', 1] name = property(get_name, None, None, None) def get_slug_type(self): - - ''' + """ Returns the type of slugs being stored in the bucket (inner slug or outer slug). Returns ------- slug_type: str - ''' + """ + + return self.__specs.loc["Slug type", 1] - return self.__specs.loc['Slug type', 1] slug_type = property(get_slug_type, None, None, None) def get_n_slugs(self): - - ''' + """ Returns the number of fuel slugs in the bucket. Returns ------- n_slugs: int - ''' + """ return self.__get_n_slugs() + n_slugs = property(get_n_slugs, None, None, None) def get_fuel_enrichment(self): - - ''' + """ Returns the enrichment of the fuel slugs in the bucket, in %. Returns ------- fuel_enrichment: float - ''' + """ return self.__get_fuel_enrichment() + fuel_enrichment = property(get_fuel_enrichment, None, None, None) def get_fresh_u_mass(self): - - ''' + """ Returns the total amount of uranium in the bucket, in grams. Returns ------- fresh_u_mass: float - ''' + """ return self.__get_fresh_u_mass() + fresh_u_mass = property(get_fresh_u_mass, None, None, None) def get_fresh_u238_mass(self): - - ''' + """ Returns the total amount of uranium-238 in the bucket, in grams. Returns ------- fresh_u238_mass: float - ''' + """ return self.__get_fresh_u238_mass() + fresh_u238_mass = property(get_fresh_u238_mass, None, None, None) def get_fresh_u235_mass(self): - ''' + """ Returns the total amount of uranium-235 in the bucket, in grams. Returns ------- fresh_u235_mass: float - ''' + """ return self.__GetFreshU235UMass() + fresh_u235_mass = property(get_fresh_u235_mass, None, None, None) def get_cladding_mass(self): - - ''' + """ Returns the total mass of cladding material in the bucket, in grams. Returns ------- cladding_mass: float - ''' + """ return self.__get_cladding_mass() + cladding_mass = property(get_cladding_mass, None, None, None) # End: Pre-irradiation information - #------ + # ------ def get_slug_length(self): - ''' + """ Returns the length of each slug in the fuel bucket. Returns ------- slug_length: float - ''' + """ return self.__get_slug_length() def set_slug_length(self, x): - ''' + """ Sets the length of all slugs in the bucket to x. Used for chopping. Parameters ---------- x: float - ''' + """ self.__set_slug_length(x) + slug_length = property(get_slug_length, set_slug_length, None, None) def get_outer_slug_od(self): - ''' + """ Returns the outer diameter of the outer section of fuel, in cm. A fuel slug consists of an outer section of fuel and an inner section of fuel, with cladding on the outside of the slug and between the inner and @@ -194,330 +194,346 @@ def get_outer_slug_od(self): Returns ------- outer_slug_od: float - ''' + """ return self.__get_outer_slug_od() + outer_slug_od = property(get_outer_slug_od, None, None, None) def get_outer_slug_id(self): - ''' + """ Returns the inner diameter of the outer section of fuel, in cm. Returns ------- outer_slug_id: float - ''' + """ return self.__get_outer_slug_id() + outer_slug_id = property(get_outer_slug_id, None, None, None) def get_inner_slug_od(self): - ''' + """ Returns the outer diameter of the inner section of fuel, in cm. Returns ------- inner_slug_od: float - ''' + """ return self.__get_inner_slug_od() + inner_slug_od = property(get_inner_slug_od, None, None, None) def get_inner_slug_id(self): - ''' + """ Returns the inner diameter of the inner section of fuel, in cm. Returns ------- inner_slug_id: float - ''' + """ return self.__get_inner_slug_id() + inner_slug_id = property(get_inner_slug_id, None, None, None) def get_cladding_wall_thickness(self): - ''' + """ Returns the thickness of the cladding wall which is on the outside of every fuel slug, and in between both sections of fuel, in cm. Returns ------- cladding_wall_thickness: float - ''' + """ return self.__get_cladding_wall_thickness() - cladding_wall_thickness = property( - get_cladding_wall_thickness, None, None, None) + + cladding_wall_thickness = property(get_cladding_wall_thickness, None, None, None) def get_cladding_end_thickness(self): - ''' + """ Gets the thickness of the hemispherical cladding end caps that are placed on the top and bottom of the fuel slug, in cm. Returns ------- cladding_end_thickness: float - ''' + """ return self.__get_cladding_end_thickness() + cladding_end_thickness = property(get_cladding_end_thickness, None, None, None) def get_slug_fuel_volume(self): - ''' + """ Returns the volume of fuel present in a single fuel slug, in cm^3. Returns ------- slug_fuel_volume: float - ''' + """ return self.__get_slug_fuel_volume() + slug_fuel_volume = property(get_slug_fuel_volume, None, None, None) def get_fuel_volume(self): - ''' + """ Returns the total volume of fuel in the entire bucket, in cm^3. Returns ------- fuel_volume: float - ''' + """ return self.__get_fuel_volume() + fuel_volume = property(get_fuel_volume, None, None, None) def get_slug_cladding_volume(self): - ''' + """ Returns the volume of cladding present in a single fuel slug, in cm^3. Returns ------- slug_cladding_volume: float - ''' + """ return self.__get_slug_cladding_volume() + slug_cladding_volume = property(get_slug_cladding_volume, None, None, None) def get_cladding_volume(self): - ''' + """ Returns the total volume of cladding in the bucket, in cm^3. Returns ------- cladding_volume: float - ''' + """ return self.__get_cladding_volume() + cladding_volume = property(get_cladding_volume, None, None, None) def get_fuel_mass(self): - - ''' + """ Returns the total mass of fuel in the solid phase in the bucket. Returns ------- fuel_mass: float - ''' + """ + + return self.__fuel_phase.GetValue("mass") # mass of the solid phase - return self.__fuel_phase.GetValue('mass') # mass of the solid phase fuel_mass = property(get_fuel_mass, None, None, None) def get_fuel_mass_unit(self): - ''' + """ Returns the unit that is used to measure the mass of fuel in the bucket. Returns ------- fuel_mass_unit: str - ''' + """ + + return self.__fuel_phase.GetQuantity("mass").unit # mass of the solid phase - return self.__fuel_phase.GetQuantity('mass').unit # mass of the solid phase fuel_mass_unit = property(get_fuel_mass_unit, None, None, None) - def get_radioactivity(self): # radioactivity of the fuel bucket (fix me) - ''' + def get_radioactivity(self): # radioactivity of the fuel bucket (fix me) + """ Returns the radioactivity of the fuel bucket, in units of curies. Returns ------- radioactivity: float - ''' + """ + + return self.__fuel_phase.GetValue("radioactivity") - return self.__fuel_phase.GetValue('radioactivity') radioactivity = property(get_radioactivity, None, None, None) def get_gamma_pwr(self): - ''' + """ Returns the amount of gamma radiation given off by the fuel bucket, in units of watts. Returns ------- gamma_pwr: float - ''' + """ + + return self.__fuel_phase.GetValue("gamma") # gamma pwr of the fuel bucket - return self.__fuel_phase.GetValue('gamma') # gamma pwr of the fuel bucket gamma_pwr = property(get_gamma_pwr, None, None, None) def get_heat_pwr(self): - ''' + """ Returns the total amount of heat generated by the bucket, in units of watts. Returns ------- heat_pwr: float - ''' + """ + + return self.__fuel_phase.GetValue("heat") # heat pwr of the fuel bucket - return self.__fuel_phase.GetValue('heat') # heat pwr of the fuel bucket heat_pwr = property(get_heat_pwr, None, None, None) def get_fuel_radioactivity(self): # radioactivity of the solid phase - ''' + """ Returns the total radioactivity of the solid phase fuel, in units of curies. Returns ------- fuel_radioactivity: float - ''' + """ + + return self.__fuel_phase.GetValue("radioactivity") - return self.__fuel_phase.GetValue('radioactivity') fuel_radioactivity = property(get_fuel_radioactivity, None, None, None) def get_fuel_phase(self): - - ''' + """ Returns the phase history of the fuel. Returns ------- fuel_phase: pandas.core.frame.DataFrame - ''' + """ return self.__fuel_phase def set_fuel_phase(self, phase): - ''' + """ Sets the current fuel phase to a specified phase value. Parameters ---------- phase: dataFrame - ''' + """ self.__fuel_phase = deepcopy(phase) + fuel_phase = property(get_fuel_phase, set_fuel_phase, None, None) def get_cladding_phase(self): - ''' + """ Returns the phase history of the cladding. Returns ------- cladding_phase: dataFrame - ''' + """ return self.__cladding_phase def set_cladding_phase(self, phase): - ''' + """ Set's the phase history to specific values. Parameters ---------- phase: dataFrame - ''' + """ self.__cladding_phase = deepcopy(phase) + cladding_phase = property(get_cladding_phase, set_cladding_phase, None, None) -#********************************************************************************** -# Private helper functions (internal use: __) -#********************************************************************************** + # ********************************************************************************** + # Private helper functions (internal use: __) + # ********************************************************************************** def __get_n_slugs(self): - ''' + """ Returns the number of fuel slugs in the bucket. Returns ------- self.__specs.loc['Number of slugs', 1]: int - ''' + """ - return int(self.__specs.loc['Number of slugs', 1]) + return int(self.__specs.loc["Number of slugs", 1]) def __get_fuel_enrichment(self): - ''' + """ Returns the enrichment of the fuel in the bucket, in %. Returns ------- self.__specs.loc['Enrichment [U-235 wt%]', 1]: float - ''' + """ - return float(self.__specs.loc['Enrichment [U-235 wt%]', 1]) + return float(self.__specs.loc["Enrichment [U-235 wt%]", 1]) def __get_outer_slug_fresh_u_mass(self): - ''' + """ Returns the the mass of uranium present in the outer part of a fuel slug, in grams. Returns ------- self.__specs.loc['U mass outer slug [kg]', 1]) * 1000: float - ''' + """ - return float(self.__specs.loc['U mass outer slug [kg]', 1]) * 1000.0 # [g] + return float(self.__specs.loc["U mass outer slug [kg]", 1]) * 1000.0 # [g] def __get_inner_slug_fresh_u_mass(self): - ''' + """ Returns the mass of uranium present in the inner part of the fuel slug, in grams. Returns ------- self.__specs.loc['U mass inner slug [kg]', 1]) * 1000: float - ''' + """ - return float( - self.__specs.loc['U mass inner slug [kg]', 1]) * 1000.0 # [g] + return float(self.__specs.loc["U mass inner slug [kg]", 1]) * 1000.0 # [g] def __get_outer_slug_cladding_mass(self): - ''' + """ Returns the mass of cladding present in the outer part of the slug, in grams. Returns ------- self.__specs.loc['Cladding mass outer slug [kg]', 1]) * 1000: float - ''' + """ - return float( - self.__specs.loc['Cladding mass outer slug [kg]', 1]) * 1000.0 # [g] + return ( + float(self.__specs.loc["Cladding mass outer slug [kg]", 1]) * 1000.0 + ) # [g] def __get_inner_slug_cladding_mass(self): - ''' + """ Returns the mass of cladding present in the inner part of the slug, in grams. - ''' + """ - return float( - self.__specs.loc['Cladding mass inner slug [kg]', 1]) * 1000.0 # [g] + return ( + float(self.__specs.loc["Cladding mass inner slug [kg]", 1]) * 1000.0 + ) # [g] def __get_fresh_u_mass(self): - ''' + """ Returns the total amount of uranium present in the fuel bucket, in grams. Returns ------- n_slugs * (uMassOuterSlug + uMassInnerSlug): float - ''' + """ n_slugs = self.__get_n_slugs() uMassOuterSlug = self.__get_outer_slug_fresh_u_mass() @@ -525,40 +541,40 @@ def __get_fresh_u_mass(self): return n_slugs * (uMassOuterSlug + uMassInnerSlug) def __get_fresh_u238_mass(self): - ''' + """ Returns the total mass of uranium-238 present in the fuel bucket, in grams. Returns ------- totalUMass * (1.0 - fuelEnrichment / 100): float - ''' + """ totalUMass = self.__get_fresh_u_mass() fuelEnrichment = self.__get_fuel_enrichment() return totalUMass * (1.0 - fuelEnrichment / 100.0) def __get_fresh_u235_mass(self): - ''' + """ Returns the total amount of uranium-235 present in the bucket in grams. Returns ------- totalUmass * fuelEnrichment / 100: float - ''' + """ totalUMass = self.__get_fresh_u_mass() fuelEnrichment = self.__get_fuel_enrichment() return totalUMass * fuelEnrichment / 100.0 def __get_cladding_mass(self): - ''' + """ Returns the total amount of cladding present in the bucket, in grams. Returns ------- n_slugs * (cladMassOuterSlug + cladMassInnerSlug): float - ''' + """ n_slugs = self.__get_n_slugs() cladMassOuterSlug = self.__get_outer_slug_cladding_mass() @@ -566,107 +582,107 @@ def __get_cladding_mass(self): return n_slugs * (cladMassOuterSlug + cladMassInnerSlug) def __get_slug_length(self): - ''' + """ Returns the length of a fuel slug, in cm. Does NOT include end caps. Returns ------- self.__specs.loc['Slug length [in]', 1]) * 2.54: float - ''' + """ - return float(self.__specs.loc['Slug length [in]', 1]) * 2.54 # cm + return float(self.__specs.loc["Slug length [in]", 1]) * 2.54 # cm def __set_slug_length(self, x): - ''' + """ Sets the length of a fuel slug to a specified value. Used for chopping. Parameters ---------- x: float - ''' + """ - self.__specs.loc['Slug length [in]', 1] = x / 2.54 # in + self.__specs.loc["Slug length [in]", 1] = x / 2.54 # in return def __get_outer_slug_od(self): - ''' + """ Returns the outer diameter of the outer fuel section of the slug in cm. Returns ------- self.__specs.loc['Outer slug O.D. [in]', 1]) * 2.54: float - ''' + """ - return float(self.__specs.loc['Outer slug O.D. [in]', 1]) * 2.54 # cm + return float(self.__specs.loc["Outer slug O.D. [in]", 1]) * 2.54 # cm def __get_outer_slug_id(self): - ''' + """ Returns the inner diameter of the outer fuel section of the slug in cm. Returns ------- self.__specs.loc['Outer slug I.D. [in]', 1]) * 2.54: float - ''' + """ - return float(self.__specs.loc['Outer slug I.D. [in]', 1]) * 2.54 # cm + return float(self.__specs.loc["Outer slug I.D. [in]", 1]) * 2.54 # cm def __get_inner_slug_od(self): - ''' + """ Returns the outer diameter of the inner fuel section of the slug in cm. Returns ------- self.__specs.loc['Inner slug O.D. [in]', 1]) * 2.54: float - ''' + """ - return float(self.__specs.loc['Inner slug O.D. [in]', 1]) * 2.54 # cm + return float(self.__specs.loc["Inner slug O.D. [in]", 1]) * 2.54 # cm def __get_inner_slug_id(self): - ''' + """ Returns the inner diameter of the inner fuel section of the slug in cm. Returns ------- self.__specs.loc['Inner slug I.D. [in]', 1]) * 2.54: float - ''' + """ - return float(self.__specs.loc['Inner slug I.D. [in]', 1]) * 2.54 # cm + return float(self.__specs.loc["Inner slug I.D. [in]", 1]) * 2.54 # cm def __get_cladding_wall_thickness(self): - ''' + """ Returns the thickness of the cladding material used on the outside of the fuel slug and between the inner and outer fuel sections. Returns ------- self.__specs.loc['Cladding wall thickness [mm]', 1]) /10: float - ''' + """ - return float( - self.__specs.loc['Cladding wall thickness [mm]', 1]) / 10.0 # cm + return float(self.__specs.loc["Cladding wall thickness [mm]", 1]) / 10.0 # cm def __get_cladding_end_thickness(self): - ''' + """ Returns the thickness of the hemispherical end caps placed on either end of the cylindrical fuel slug, in cm. Returns ------- self.__specs.loc['Cladding end cap thickness [mm]', 1]) / 10: float - ''' + """ - return float( - self.__specs.loc['Cladding end cap thickness [mm]', 1]) / 10.0 # cm + return ( + float(self.__specs.loc["Cladding end cap thickness [mm]", 1]) / 10.0 + ) # cm def __get_slug_fuel_volume(self): - ''' + """ Returns the volume of fuel contained within a single fuel slug, in cm^3. Returns ------- outerVolume + innerVolume: float - ''' + """ slugLength = self.__get_slug_length() cladWallThickness = self.__get_cladding_wall_thickness() @@ -674,91 +690,99 @@ def __get_slug_fuel_volume(self): fuelLength = slugLength - 2.0 * cladEndThickness fuelOuterSlugOuterRadius = self.__get_outer_slug_od() / 2.0 - cladWallThickness fuelOuterSlugInnerRadius = self.__get_outer_slug_id() / 2.0 + cladWallThickness - outerVolume = fuelLength * math.pi * \ - (fuelOuterSlugOuterRadius**2 - fuelOuterSlugInnerRadius**2) + outerVolume = ( + fuelLength + * math.pi + * (fuelOuterSlugOuterRadius**2 - fuelOuterSlugInnerRadius**2) + ) fuelInnerSlugOuterRadius = self.__get_inner_slug_od() / 2.0 - cladWallThickness fuelInnerSlugInnerRadius = self.__get_inner_slug_id() / 2.0 + cladWallThickness - innerVolume = fuelLength * math.pi * \ - (fuelInnerSlugOuterRadius**2 - fuelInnerSlugInnerRadius**2) + innerVolume = ( + fuelLength + * math.pi + * (fuelInnerSlugOuterRadius**2 - fuelInnerSlugInnerRadius**2) + ) return outerVolume + innerVolume def __get_slug_volume(self): - ''' + """ Returns the total volume of a single fuel slug, in cm^3. Does not include the end caps. - ''' + """ slugLength = self.__get_slug_length() outerSlugOuterRadius = self.__get_outer_slug_od() / 2.0 outerSlugInnerRadius = self.__get_outer_slug_id() / 2.0 - outerVolume = slugLength * math.pi * \ - (outerSlugOuterRadius**2 - outerSlugInnerRadius**2) + outerVolume = ( + slugLength * math.pi * (outerSlugOuterRadius**2 - outerSlugInnerRadius**2) + ) innerSlugOuterRadius = self.__get_inner_slug_od() / 2.0 innerSlugInnerRadius = self.__get_inner_slug_id() / 2.0 - innerVolume = slugLength * math.pi * \ - (innerSlugOuterRadius**2 - innerSlugInnerRadius**2) + innerVolume = ( + slugLength * math.pi * (innerSlugOuterRadius**2 - innerSlugInnerRadius**2) + ) return outerVolume + innerVolume def __get_slug_cladding_volume(self): - ''' + """ Returns the total volume of cladding in a single fuel slug. Does not include the end caps. Given in units of cm^3. Returns ------- self.__get_slug_volume() - self.__get_slug_fuel_volume(): float - ''' + """ return self.__get_slug_volume() - self.__get_slug_fuel_volume() def __get_fuel_volume(self): - ''' + """ Returns the total volume of fuel in the bucket, in cm^3. Returns ------- slugFuelVolume * nFuelSlugs: float - ''' + """ slugFuelVolume = self.__get_slug_fuel_volume() nFuelSlugs = self.__get_n_slugs() return slugFuelVolume * nFuelSlugs def __get_cladding_volume(self): - ''' + """ Returns the total volume of cladding material in the bucket, in cm^3. Does not include end caps. Returns ------- slugCladdingVolume * nFuelSlugs - ''' + """ slugCladdingVolume = self.__get_slug_cladding_volume() nFuelSlugs = self.__get_n_slugs() return slugCladdingVolume * nFuelSlugs def __str__(self): - ''' + """ Converts to string. - ''' + """ - s = '\nFuelBucket():\n\t*******\n\t specs:\n\t*******\n\t %s\n' - t = '\t************\n\t fuel_phase:\n\t**********\n\t %s\n' - u = '\t***************\n\t cladding_phase:\n\t*****************\n\t %s\n' + s = "\nFuelBucket():\n\t*******\n\t specs:\n\t*******\n\t %s\n" + t = "\t************\n\t fuel_phase:\n\t**********\n\t %s\n" + u = "\t***************\n\t cladding_phase:\n\t*****************\n\t %s\n" stu = s + t + u return stu % (self.__specs, self.__fuel_phase, self.__cladding_phase) def __repr__(self): - - ''' + """ Converts to string. - ''' + """ - s = '\nFuelBucket():\n\t*******\n\t specs:\n\t*******\n\t %s\n' - t = '\t************\n\t fuel_phase:\n\t**********\n\t %s\n' - u = '\t****************\n\t cladding_phase:\n\t****************\n\t %s\n' + s = "\nFuelBucket():\n\t*******\n\t specs:\n\t*******\n\t %s\n" + t = "\t************\n\t fuel_phase:\n\t**********\n\t %s\n" + u = "\t****************\n\t cladding_phase:\n\t****************\n\t %s\n" stu = s + t + u return stu % (self.__specs, self.__fuel_phase, self.__cladding_phase) -#======================= end class FuelBucket =================================== + +# ======================= end class FuelBucket =================================== diff --git a/cortix/support/nuclear/fuel_bundle.py b/cortix/support/nuclear/fuel_bundle.py index fbad8dff..df760bfe 100644 --- a/cortix/support/nuclear/fuel_bundle.py +++ b/cortix/support/nuclear/fuel_bundle.py @@ -8,7 +8,7 @@ # # Licensed under the University of Massachusetts Lowell LICENSE: # https://github.com/dpploy/cortix/blob/master/LICENSE.txt -''' +""" This FuelBundle class is a container for usage with other plant-level process modules. It is meant to represent a fuel bundle of an oxide fuel LWR reactor. There are three main data structures: @@ -23,26 +23,24 @@ Author: Valmor de Almeida dealmeidav@ornl.gov; vfda Sun Dec 27 15:06:55 EST 2015 -''' -#********************************************************************************* +""" + +# ********************************************************************************* import os import sys import math import pandas from copy import deepcopy -#********************************************************************************* - -class FuelBundle(): +# ********************************************************************************* -#********************************************************************************* -# Constructor -#********************************************************************************* - def __init__(self, - specs=pandas.DataFrame() - ): +class FuelBundle: + # ********************************************************************************* + # Constructor + # ********************************************************************************* - assert isinstance(specs, pandas.DataFrame), 'oops not pandas table.' + def __init__(self, specs=pandas.DataFrame()): + assert isinstance(specs, pandas.DataFrame), "oops not pandas table." self.__specs = specs @@ -54,392 +52,407 @@ def __init__(self, # ------ # Start: Pre-irradiation information -#********************************************************************************* -# Public Member Functions -#********************************************************************************* + # ********************************************************************************* + # Public Member Functions + # ********************************************************************************* def get_name(self): - ''' + """ Returns the name of the fuel bundle. Returns ------- name: str - ''' + """ + + return self.__specs.loc["Name", 1] - return self.__specs.loc['Name', 1] name = property(get_name, None, None, None) def get_fuel_enrichment(self): - ''' + """ Returns the enrichment of the fuel pins in the bundle, in %. Returns ------- fuel_enrichment: float - ''' + """ return self.__get_fuel_enrichment() + fuel_enrichment = property(get_fuel_enrichment, None, None, None) def get_fresh_u_mass(self): - ''' + """ Returns the amount of uranium in the bundle, in grams. Returns ------- fresh_u_mass: float - ''' + """ return self.__get_fresh_u_mass() + fresh_u_mass = property(get_fresh_u_mass, None, None, None) def get_fresh_u238_mass(self): - ''' + """ Returns the amount of uranium-238 in the bucket, in grams. Returns ------- fresh_u238_mass: float - ''' + """ total_mass = self.__get_fresh_u_mass() fuel_enrichment = self.__get_fuel_enrichment() return total_mass * (1.0 - fuel_enrichment / 100.0) + fresh_u238_mass = property(get_fresh_u238_mass, None, None, None) def get_fresh_U235_mass(self): - ''' + """ Returns the amount of uranium-235 in the bucket, in grams. Returns ------- fresh_u235_mass: float - ''' + """ total_u_Mass = self.__get_fresh_u_mass() fuel_enrichment = self.__get_fuel_enrichment() return total_u_mass * fuel_enrichment / 100.0 + fresh_u235_mass = property(get_fresh_U235_mass, None, None, None) def get_n_fuel_rods(self): - ''' + """ Returns the number of fuel rods in the bundle. Returns ------- n_fuel_rods: int - ''' + """ return self.__get_n_fuel_rods() + n_fuel_rods = property(get_n_fuel_rods, None, None, None) # End: Pre-irradiation information # ------ def get_fuel_pin_length(self): - ''' + """ Returns the length of each fuel pin in the fuel bundle. A fuel pin is a cylindircal section of uranium fuel that is surrounded by cladding. Returns ------- fuel_pin_length: float - ''' + """ return self.__get_fuel_pin_length() def set_fuel_pin_length(self, x): - ''' + """ Sets the length of all fuel pins in the bundle to x. Returns ------- x: float - ''' + """ - self.__specs.loc['Fuel rods fuel length [in]', 1] = x / 2.54 # in + self.__specs.loc["Fuel rods fuel length [in]", 1] = x / 2.54 # in return + fuel_pin_length = property(get_fuel_pin_length, set_fuel_pin_length, None, None) def get_fuel_rod_od(self): - ''' + """ Returns the outer diameter of the fuel rod, in cm. A fuel rod consists of a fuel pin surrounded by cladding. Returns ------- fuel_rod_od: float - ''' + """ return self.__get_fuel_rod_od() + fuel_rod_od = property(get_fuel_rod_od, None, None, None) def get_fuel_pin_radius(self): - ''' + """ Returns the radius of the fuel pin, in cm. - ''' + """ return self.__get_fuel_pin_radius() + fuel_pin_radius = property(get_fuel_pin_radius, None, None, None) def get_fuel_pin_volume(self): - ''' + """ Returns the volume of fuel in each fuel pin, in cm^3. Returns ------- fuel_pin_volume: float - ''' + """ return self.__get_fuel_pin_volume() + fuel_pin_volume = property(get_fuel_pin_volume, None, None, None) def get_fuel_volume(self): - - ''' + """ Returns the total volume of fuel in the bundle, in cm^3. Returns ------- fuel_volume: float - ''' + """ return self.__get_fuel_volume() + fuel_volume = property(get_fuel_volume, None, None, None) # mass of the solid phase (gas phase in plenum not added) def get_fuel_mass(self): - ''' + """ Returns the total numerical value for mass of fuel in the solid phase in the bundle. Returns ------- fuel_mass: float - ''' + """ + + return self.__solid_phase.GetValue("mass") - return self.__solid_phase.GetValue('mass') fuel_mass = property(get_fuel_mass, None, None, None) def get_fuel_mass_unit(self): - ''' + """ Returns the unit used to measure the mass of fuel in the bundle. Returns ------- fuel_mass_unit: str - ''' + """ + + return self.__solid_phase.GetQuantity("mass").unit - return self.__solid_phase.GetQuantity('mass').unit fuel_mass_unit = property(get_fuel_mass_unit, None, None, None) def get_gas_mass(self): - ''' + """ Returns the total numerical value for mass of the fuel in the gas phase. - ''' + """ + + return self.__gas_phase.GetValue("mass") - return self.__gas_phase.GetValue('mass') gas_mass = property(get_gas_mass, None, None, None) def get_radioactivity(self): - ''' + """ Returns the total radioactivity of the fuel bundle, in curies. Returns ------- raduioactivity: float - ''' + """ + + return self.__solid_phase.GetValue("radioactivity") + self.__gas_phase.GetValue( + "radioactivity" + ) - return self.__solid_phase.GetValue('radioactivity') + \ - self.__gas_phase.GetValue('radioactivity') radioactivity = property(get_radioactivity, None, None, None) def get_gamma_pwr(self): - ''' + """ Returns the total amount of gamma radiation given by the fuel bundle, in watts. Returns ------- gamma_pwr: float - ''' + """ + + return self.__solid_phase.GetValue("gamma") + self.__gas_phase.GetValue("gamma") - return self.__solid_phase.GetValue('gamma') + \ - self.__gas_phase.GetValue('gamma') gamma_pwr = property(get_gamma_pwr, None, None, None) def get_heat_pwr(self): - ''' + """ Returns the total amount of heat produced by the fuel bundle, in watts. Returns ------- heat_pwr: float - ''' + """ + + return self.__solid_phase.GetValue("heat") + self.__gas_phase.GetValue("heat") - return self.__solid_phase.GetValue('heat') + \ - self.__gas_phase.GetValue('heat') heat_pwr = property(get_heat_pwr, None, None, None) def get_fuel_radioactivity(self): - # radioactivity of the solid phase - ''' + # radioactivity of the solid phase + """ Returns the total radioactivity of the fuel in the solid phase in the fuel bundle. Returns ------- fuel_radioactivity: float - ''' + """ + + return self.__solid_phase.GetValue("radioactivity") - return self.__solid_phase.GetValue('radioactivity') fuel_radioactivity = property(get_fuel_radioactivity, None, None, None) def get_gas_radioactivity(self): # radioactivity of the gas phase - ''' + """ Returns the total radioactivity of the fuel in the gas phase in the fuel bundle, in curies. Returns ------- gas_radioactivity: float - ''' + """ + + return self.__gas_phase.GetValue("radioactivity") - return self.__gas_phase.GetValue('radioactivity') gas_radioactivity = property(get_gas_radioactivity, None, None, None) def get_solid_phase(self): - ''' + """ Returns the solid phase history associated with this fuel bundle. Returns ------- solidPhase: dataFrame - ''' + """ return self.__solid_phase def set_solid_phase(self, phase): - ''' + """ Sets the solid phase history of the fuel equal to phase. Parameters ---------- phase: dataFrame - ''' + """ self.__solid_phase = deepcopy(phase) + solid_phase = property(get_solid_phase, set_solid_phase, None, None) def get_gas_phase(self): - ''' + """ Returns the gas phase history of the fuel. Returns ------- gas_phase: dataFrame - ''' + """ return self.__gas_phase def set_gas_phase(self, phase): - ''' + """ Sets the gas phase history of the fuel equal to phase. Parameters ---------- phase: dataFrame - ''' + """ self.__gas_phase = deepcopy(phase) + gas_phase = property(get_gas_phase, set_gas_phase, None, None) -#********************************************************************************* -# Private helper functions (internal use: __) -#********************************************************************************* + # ********************************************************************************* + # Private helper functions (internal use: __) + # ********************************************************************************* def __get_fuel_enrichment(self): - ''' + """ Returns the enrichment of the fuel. Returns ------- self.__specs.loc['Enrichment [U-235 wt%]', 1]: float - ''' + """ - return float(self.__specs.loc['Enrichment [U-235 wt%]', 1]) + return float(self.__specs.loc["Enrichment [U-235 wt%]", 1]) def __get_fresh_u_mass(self): - - ''' + """ Returns the mass of fuel in the bundle. Returns ------- self.__specs.loc['U mass per assy [kg]', 1]) * 1000 - ''' + """ - return float( - self.__specs.loc['U mass per assy [kg]', 1]) * 1000.0 # [g] + return float(self.__specs.loc["U mass per assy [kg]", 1]) * 1000.0 # [g] def __get_n_fuel_rods(self): - ''' + """ Returns the number of fuel rods in the bundle. Returns ------- self.__specs.loc['Fuel rods number', 1]): int - ''' + """ - return int(self.__specs.loc['Fuel rods number', 1]) + return int(self.__specs.loc["Fuel rods number", 1]) def __get_fuel_pin_length(self): - ''' + """ Returns the length of the fuel pins in the bundle. Returns ------- self.__specs.loc["fuel rods fuel length [in]', 1]) * 2.54 - ''' + """ - return float( - self.__specs.loc['Fuel rods fuel length [in]', 1]) * 2.54 # cm + return float(self.__specs.loc["Fuel rods fuel length [in]", 1]) * 2.54 # cm def __get_fuel_rod_od(self): - ''' + """ Returns the outer diameter of the fuel rods in the bundle. Returns ------- self.__specs.loc['Fuel rods O.D. [in]', 1]) * 2.54: float - ''' + """ - return float(self.__specs.loc['Fuel rods O.D. [in]', 1]) * 2.54 # cm + return float(self.__specs.loc["Fuel rods O.D. [in]", 1]) * 2.54 # cm def __get_fuel_rod_wall_thickness(self): - ''' + """ Returns the thickness of the cladding wall on the outside of the fuel rod. Returns ------- self.__specs.loc['Fuel rods wall thickness [in]', 1]) * 2.54: float - ''' + """ - return float( - self.__specs.loc['Fuel rods wall thickness [in]', 1]) * 2.54 # cm + return float(self.__specs.loc["Fuel rods wall thickness [in]", 1]) * 2.54 # cm def __get_fuel_pin_radius(self): - ''' + """ Returns the radius of the fuel pins. Returns ------- fuel_pin_radius: float - ''' + """ fuel_rod_od = self.__get_fuel_rod_od() fuel_rod_wall_thickness = self.__get_fuel_rod_wall_thickness() @@ -447,38 +460,38 @@ def __get_fuel_pin_radius(self): return fuel_pin_radius def __get_fuel_pin_volume(self): - ''' + """ Returns the volume of each fuel pin. Returns ------- fuel_pin_length * math.pi * fuel_pin_radius ** 2: float - ''' + """ fuel_pin_length = self.__get_fuel_pin_length() fuel_pin_radius = self.__get_fuel_pin_radius() - return fuel_pin_length * math.pi * fuel_pin_radius ** 2 + return fuel_pin_length * math.pi * fuel_pin_radius**2 def __get_fuel_volume(self): - - ''' + """ Returns the volume of fuel in the bundle, in cm^3. Returns ------- fuel_pin_volume * n_fuel_rods: float - ''' + """ fuel_pin_volume = self.__get_fuel_pin_volume() n_fuel_rods = self.__get_n_fuel_rods() return fuel_pin_volume * n_fuel_rods def __str__(self): - s = 'FuelBundle():\n %s\n %s\n %s\n' + s = "FuelBundle():\n %s\n %s\n %s\n" return s % (self.__specs, self.__solid_phase, self.__gas_phase) def __repr__(self): - s = 'FuelBundle():\n %s\n %s\n %s\n' + s = "FuelBundle():\n %s\n %s\n %s\n" return s % (self.__specs, self.__solid_phase, self.__gas_phase) -#======================= end class FuelBundle ==================================== + +# ======================= end class FuelBundle ==================================== diff --git a/cortix/support/nuclear/fuel_segment.py b/cortix/support/nuclear/fuel_segment.py index d44bf67f..f1f72d8b 100644 --- a/cortix/support/nuclear/fuel_segment.py +++ b/cortix/support/nuclear/fuel_segment.py @@ -8,12 +8,13 @@ # # Licensed under the University of Massachusetts Lowell LICENSE: # https://github.com/dpploy/cortix/blob/master/LICENSE.txt -''' +""" Fuel segment Author: Valmor de Almeida dealmeidav@ornl.gov; vfda Sat Jun 27 14:46:49 EDT 2015 -''' -#********************************************************************************* +""" + +# ********************************************************************************* import os import sys import io @@ -26,90 +27,88 @@ from cortix.support.periodictable import ELEMENTS from cortix.support.periodictable import SERIES from cortix.support.specie import Specie -#********************************************************************************* +# ********************************************************************************* -class FuelSegment(): +class FuelSegment: # TODO: Species should not be here. Need to replace by Phase instead. # Chopper will be affected -#********************************************************************************* -# Construction -#********************************************************************************* - - def __init__(self, - geometry = pandas.Series(), - species = list() - ): + # ********************************************************************************* + # Construction + # ********************************************************************************* - assert isinstance(geometry, pandas.Series), 'fatal.' - assert isinstance(species, list), 'fatal.' + def __init__(self, geometry=pandas.Series(), species=list()): + assert isinstance(geometry, pandas.Series), "fatal." + assert isinstance(species, list), "fatal." if isinstance(species, list) and len(species) > 0: assert isinstance(species[0], Specie) - self.attribute_names = \ - ['n-segments', - 'fuel-volume', - 'segment-volume', - 'fuel-diameter', - 'fuel-length', - 'mass', - 'mass-dens', - 'mass-cc', - 'nuclides', - 'isotopes', - 'radioactivity', - 'radioactivity-dens', - 'gamma', - 'gamma-dens', - 'heat', - 'heat-dens', - 'molar-heat-pwr', - 'molar-gamma-pwr'] + self.attribute_names = [ + "n-segments", + "fuel-volume", + "segment-volume", + "fuel-diameter", + "fuel-length", + "mass", + "mass-dens", + "mass-cc", + "nuclides", + "isotopes", + "radioactivity", + "radioactivity-dens", + "gamma", + "gamma-dens", + "heat", + "heat-dens", + "molar-heat-pwr", + "molar-gamma-pwr", + ] self.__geometry = geometry - self.__species = species + self.__species = species -#********************************************************************************* -# Public Member Functions -#********************************************************************************* + # ********************************************************************************* + # Public Member Functions + # ********************************************************************************* def get_geometry(self): - - ''' + """ Returns the geometry of the fuel bundle (cylindrical, hexoganol, rectangular, etc). Returns ------- geometry: pandas.series - ''' + """ return self.__geometry + geometry = property(get_geometry, None, None, None) def get_species(self): - ''' - Returns the list containing all the specie objects that make up the - fuel bundle. -<<<<<<< HEAD:cortix/support/fuel_segment.py - - Parameters - ---------- - Empty: -======= ->>>>>>> master:cortix/support/nuclear/fuel_segment.py - - Returns - ------- - species: list of objects - ''' + """ + Returns the list containing all the specie objects that make up the + fuel bundle. + <<<<<<< HEAD:cortix/support/fuel_segment.py + + Parameters + ---------- + Empty: + ======= + >>>>>>> master:cortix/support/nuclear/fuel_segment.py + + Returns + ------- + species: list of objects + """ return self.__species + species = property(get_species, None, None, None) def get_specie(self, name): - ''' + """ Returns a specie named [name] from the list of species making up the fuel bundle. If no name is specified, this function will return None. @@ -120,18 +119,18 @@ def get_specie(self, name): Returns ------- specie: obj - ''' + """ for specie in self.__species: if specie.name == name: return specie return None + specie = property(get_specie, None, None, None) -# Get stored fuel segment property either overall or on a nuclide basis + # Get stored fuel segment property either overall or on a nuclide basis def get_attribute(self, name, nuclide=None, series=None): - - ''' + """ Used to get stored fuel segment properties, either overall (as an average), or on a nuclide basis. "name" in this case refers to the attribute in question. At this point in time, series is not implemented @@ -160,227 +159,232 @@ def get_attribute(self, name, nuclide=None, series=None): Returns ------- many types - ''' + """ attribute_name = name - assert attribute_name in self.attribute_names, \ - 'attribute_name: %r; options: %r; fail.' % \ - (attribute_name, self.attribute_names) + assert attribute_name in self.attribute_names, ( + "attribute_name: %r; options: %r; fail." + % (attribute_name, self.attribute_names) + ) if nuclide is not None: - assert isinstance(nuclide,str), 'type(nuclide) = %r' % type(nuclide) + assert isinstance(nuclide, str), "type(nuclide) = %r" % type(nuclide) # no multipliers for now (see below: codes is almost ready for it) - assert len(nuclide.split('*')) == 1, 'nuclide = %r' % nuclide # sanity check + assert len(nuclide.split("*")) == 1, ( + "nuclide = %r" % nuclide + ) # sanity check if nuclide is not None: - assert series is None, 'fail.' + assert series is None, "fail." if series is not None: - assert nuclide is None, 'fail.' + assert nuclide is None, "fail." if series is not None: - assert False, ' not implemented.' + assert False, " not implemented." - if attribute_name == 'isotopes': - assert nuclide is not None, 'need a nuclide symbol.' + if attribute_name == "isotopes": + assert nuclide is not None, "need a nuclide symbol." -# ................................................................................. -# # of segments + # ................................................................................. + # # of segments - if attribute_name == 'n-segments': + if attribute_name == "n-segments": return 1 -# ................................................................................. -# segment id + # ................................................................................. + # segment id - if attribute_name == 'segment-id': - return self.__geometry['segment id'] + if attribute_name == "segment-id": + return self.__geometry["segment id"] -# ................................................................................. -# fuel volume + # ................................................................................. + # fuel volume - if attribute_name == 'fuel-volume': + if attribute_name == "fuel-volume": return self.__get_fuel_segment_volume() -# ................................................................................. -# segment volume - - if attribute_name == 'segment-volume': + # ................................................................................. + # segment volume - cladding_length = self.__geometry['cladding length [cm]'] - cladding_diam = self.__geometry['OD [cm]'] - volume = cladding_length * math.pi * (cladding_diam / 2.0)**2 + if attribute_name == "segment-volume": + cladding_length = self.__geometry["cladding length [cm]"] + cladding_diam = self.__geometry["OD [cm]"] + volume = cladding_length * math.pi * (cladding_diam / 2.0) ** 2 return volume -# ................................................................................. -# fuel diameter + # ................................................................................. + # fuel diameter - if attribute_name == 'fuel-diameter': - - fuel_diam = self.__geometry['fuel diameter [cm]'] + if attribute_name == "fuel-diameter": + fuel_diam = self.__geometry["fuel diameter [cm]"] return fuel_diam -# ................................................................................. -# fuel length - - if attribute_name == 'fuel-length': + # ................................................................................. + # fuel length - fuel_length = self.__geometry['fuel length [cm]'] + if attribute_name == "fuel-length": + fuel_length = self.__geometry["fuel length [cm]"] return fuel_length -# ................................................................................. -# fuel segment overall quantities + # ................................................................................. + # fuel segment overall quantities if nuclide is None and series is None: - # mass or mass concentration - if attribute_name == 'mass-cc' or attribute_name == 'mass-dens' or attribute_name == 'mass': + if ( + attribute_name == "mass-cc" + or attribute_name == "mass-dens" + or attribute_name == "mass" + ): mass_cc = 0.0 for spc in self.__species: mass_cc += spc.massCC - if attribute_name == 'mass-cc' or attribute_name == 'mass-dens': + if attribute_name == "mass-cc" or attribute_name == "mass-dens": return mass_cc else: volume = self.__get_fuel_segment_volume() return mass_cc * volume -# radioactivity - if attribute_name == 'radioactivtyDens' or attribute_name == 'radioactivity': + # radioactivity + if ( + attribute_name == "radioactivtyDens" + or attribute_name == "radioactivity" + ): radDens = 0.0 for spc in self.__species: radDens += spc.molarRadioactivity * spc.molarCC - if attribute_name == 'radioactivity-dens': + if attribute_name == "radioactivity-dens": return radDens else: volume = self.__get_fuel_segment_volume() return radDens * volume -# gamma - if attribute_name == 'gamma-dens' or attribute_name == 'gamma': + # gamma + if attribute_name == "gamma-dens" or attribute_name == "gamma": gamma_dens = 0.0 for spc in self.__species: gamma_dens += spc.molarGammaPwr * spc.molarCC - if attribute_name == 'gamma-dens': + if attribute_name == "gamma-dens": return gamma_dens else: volume = self.__get_fuel_segment_volume() return gamma_dens * volume -# heat - if attribute_name == 'heat-dens' or attribute_name == 'heat': + # heat + if attribute_name == "heat-dens" or attribute_name == "heat": heat_dens = 0.0 for spc in self.__species: heat_dens += spc.molarHeatPwr * spc.molarCC - if attribute_name == 'heat-dens': + if attribute_name == "heat-dens": return heat_dens else: volume = self.__get_fuel_segment_volume() return heat_dens * volume -# ................................................................................. -# radioactivity + # ................................................................................. + # radioactivity - if attribute_name == 'radioactivity-dens' or attribute_name == 'radioactivity': + if attribute_name == "radioactivity-dens" or attribute_name == "radioactivity": assert False - colName = 'Radioactivity Dens. [Ci/cc]' + colName = "Radioactivity Dens. [Ci/cc]" -# ................................................................................. -# thermal + # ................................................................................. + # thermal - if attribute_name == 'thermalDens' or attribute_name == 'thermal' or \ - attribute_name == 'heat-dens' or attribute_name == 'heat': + if ( + attribute_name == "thermalDens" + or attribute_name == "thermal" + or attribute_name == "heat-dens" + or attribute_name == "heat" + ): assert False - colName = 'Thermal Dens. [W/cc]' + colName = "Thermal Dens. [W/cc]" -# ................................................................................. -# gamma + # ................................................................................. + # gamma - if attribute_name == 'gamma-dens' or attribute_name == 'gamma': + if attribute_name == "gamma-dens" or attribute_name == "gamma": assert False - colName = 'Gamma Dens. [W/cc]' - -# ................................................................................. -########################################################################## -# ................................................................................. - -# if attribute_name[-4:] == 'Dens' or attribute_name[-2:] == 'CC': -# attributeDens = True -# else: -# attributeDens = False - -# ................................................................................. -# all nuclide content of the fuel added - -# if nuclide is None and series is None: -# -# density = 0.0 -# -# density = self.propertyDensities[ colName ].sum() -# -# if attributeDens is False: -# volume = self.__get_fuel_segment_volume() -# prop = density * volume -# return prop -# else: -# return density - -# ................................................................................. -# get chemical element series - -# if series is not None: -# -# density = 0.0 -# -# for isotope in isotopes: -# density += self.propertyDensities.loc[isotope,colName] -# -# if attributeDens is False: -# volume = self.__get_fuel_segment_volume() -# prop = density * volume -# return prop -# else: -# return density - -# ................................................................................. -# get specific nuclide (either the isotopes of the nuclide or the specific -# isotope) property: note the most complex case handled is, say: 2*Cs-133. + colName = "Gamma Dens. [W/cc]" + + # ................................................................................. + ########################################################################## + # ................................................................................. + + # if attribute_name[-4:] == 'Dens' or attribute_name[-2:] == 'CC': + # attributeDens = True + # else: + # attributeDens = False + + # ................................................................................. + # all nuclide content of the fuel added + + # if nuclide is None and series is None: + # + # density = 0.0 + # + # density = self.propertyDensities[ colName ].sum() + # + # if attributeDens is False: + # volume = self.__get_fuel_segment_volume() + # prop = density * volume + # return prop + # else: + # return density + + # ................................................................................. + # get chemical element series + + # if series is not None: + # + # density = 0.0 + # + # for isotope in isotopes: + # density += self.propertyDensities.loc[isotope,colName] + # + # if attributeDens is False: + # volume = self.__get_fuel_segment_volume() + # prop = density * volume + # return prop + # else: + # return density + + # ................................................................................. + # get specific nuclide (either the isotopes of the nuclide or the specific + # isotope) property: note the most complex case handled is, say: 2*Cs-133. if nuclide is not None: - # a particular nuclide given (atomic number and atomic mass number) - if len(nuclide.split('-')) == 2: - - nuclideMassNumber = int(nuclide.split('-')[1].strip('m')) - nuclideSymbol = nuclide.split('-')[0] - nuclideMolarMass = ELEMENTS[nuclideSymbol].isotopes[nuclideMassNumber].mass + if len(nuclide.split("-")) == 2: + nuclideMassNumber = int(nuclide.split("-")[1].strip("m")) + nuclideSymbol = nuclide.split("-")[0] + nuclideMolarMass = ( + ELEMENTS[nuclideSymbol].isotopes[nuclideMassNumber].mass + ) mass_cc = 0.0 for spc in self.__species: - formula = spc.atoms mole_fraction = 0.0 for item in formula: - - if len(item.split('*') - ) == 1: # no multiplier (implies 1.0) - - formula_nuclide_symbol = item.split('-')[0].strip() + if len(item.split("*")) == 1: # no multiplier (implies 1.0) + formula_nuclide_symbol = item.split("-")[0].strip() if formula_nuclide_symbol == nuclideSymbol: - assert len(item.split('-')) == 2 + assert len(item.split("-")) == 2 - if item.split('*')[0].strip() == nuclide: + if item.split("*")[0].strip() == nuclide: mole_fraction = 1.0 else: mole_fraction = 0.0 - elif len(item.split('*')) == 2: # with multiplier - - formula_nuclide_symbol = item.split( - '*')[1].split('-')[0].strip() + elif len(item.split("*")) == 2: # with multiplier + formula_nuclide_symbol = ( + item.split("*")[1].split("-")[0].strip() + ) if formula_nuclide_symbol == nuclideSymbol: - assert len(item.split('*')[1].split('-')) == 2 + assert len(item.split("*")[1].split("-")) == 2 - if item.split('*')[1].strip() == nuclide: - mole_fraction = float( - item.split('*')[0].strip()) + if item.split("*")[1].strip() == nuclide: + mole_fraction = float(item.split("*")[0].strip()) else: mole_fraction = 0.0 @@ -391,105 +395,109 @@ def get_attribute(self, name, nuclide=None, series=None): return mass_cc * self.__get_fuel_segment_volume() - # chemical element given (only atomic number given) - elif len(nuclide.split('-')) == 1: - + # chemical element given (only atomic number given) + elif len(nuclide.split("-")) == 1: mass_cc = 0.0 for spc in self.__species: - formula = spc.atoms for item in formula: - mole_fraction = 0.0 - if len(item.split('*') - ) == 1: # no multiplier (implies 1.0) - - assert len(item.split('-')) == 2 - formula_nuclide_symbol = item.split('-')[0].strip() + if len(item.split("*")) == 1: # no multiplier (implies 1.0) + assert len(item.split("-")) == 2 + formula_nuclide_symbol = item.split("-")[0].strip() formula_nuclide_mass_number = int( - item.split('-')[1].strip('m')) - formula_nuclide_molar_mass = ELEMENTS[formula_nuclide_symbol].isotopes[formula_nuclide_mass_number].mass + item.split("-")[1].strip("m") + ) + formula_nuclide_molar_mass = ( + ELEMENTS[formula_nuclide_symbol] + .isotopes[formula_nuclide_mass_number] + .mass + ) if formula_nuclide_symbol == nuclide: mole_fraction = 1.0 else: mole_fraction = 0.0 - elif len(item.split('*')) == 2: # with multiplier - - assert len(item.split('*')[1].split('-')) == 2 - formula_nuclides_symbol = item.split( - '*')[1].split('-')[0].strip() + elif len(item.split("*")) == 2: # with multiplier + assert len(item.split("*")[1].split("-")) == 2 + formula_nuclides_symbol = ( + item.split("*")[1].split("-")[0].strip() + ) formula_nuclide_mass_number = int( - item.split('*')[1].split('-')[1].strip('m')) - formula_nuclide_molar_mass = \ - ELEMENTS[formula_nuclides_symbol].isotopes[formula_nuclide_mass_number].mass + item.split("*")[1].split("-")[1].strip("m") + ) + formula_nuclide_molar_mass = ( + ELEMENTS[formula_nuclides_symbol] + .isotopes[formula_nuclide_mass_number] + .mass + ) if formula_nuclides_symbol == nuclide: - mole_fraction = float( - item.split('*')[0].strip()) + mole_fraction = float(item.split("*")[0].strip()) else: mole_fraction = 0.0 else: assert False - mass_cc += spc.molarCC * mole_fraction * formula_nuclide_molar_mass + mass_cc += ( + spc.molarCC * mole_fraction * formula_nuclide_molar_mass + ) return mass_cc * self.__get_fuel_segment_volume() else: - assert False -#********************************************************************************** -# Private Helper Functions (internal use: __) -#********************************************************************************* + # ********************************************************************************** + # Private Helper Functions (internal use: __) + # ********************************************************************************* def __get_fuel_segment_volume(self): - - ''' + """ Returns the total volume of fuel in the fuel segment. Returns ------- volume: float - ''' + """ - fuel_length = self.__geometry['fuel length [cm]'] - fuel_diam = self.__geometry['fuel diameter [cm]'] + fuel_length = self.__geometry["fuel length [cm]"] + fuel_diam = self.__geometry["fuel diameter [cm]"] - volume = fuel_length * math.pi * (fuel_diam / 2.0)**2 + volume = fuel_length * math.pi * (fuel_diam / 2.0) ** 2 return volume def __str__(self): - ''' + """ Used to print the geometry of the fuel segment and the species that it consists of. Returns ------- s: str - ''' + """ - s = 'FuelSegment(): %s\n %s\n' + s = "FuelSegment(): %s\n %s\n" return s % (self.__geometry, self.__species) def __repr__(self): - ''' + """ Used to print the geometry of the fuel segment and the species that it consists of. Returns ------- s: str - ''' + """ - s = 'FuelSegment(): %s\n %s\n' + s = "FuelSegment(): %s\n %s\n" return s % (self.__geometry, self.__species) -#============================ end class FuelSegment ============================== + +# ============================ end class FuelSegment ============================== diff --git a/cortix/support/nuclear/fuelsegmentsgroups.py b/cortix/support/nuclear/fuelsegmentsgroups.py index c9b4f700..ed020bbb 100644 --- a/cortix/support/nuclear/fuelsegmentsgroups.py +++ b/cortix/support/nuclear/fuelsegmentsgroups.py @@ -8,7 +8,7 @@ # # Licensed under the University of Massachusetts Lowell LICENSE: # https://github.com/dpploy/cortix/blob/master/LICENSE.txt -''' +""" Author: Valmor de Almeida dealmeidav@ornl.gov; vfda Fuel segment @@ -16,8 +16,9 @@ VFdALib support classes Sat Jun 27 14:46:49 EDT 2015 -''' -#********************************************************************************* +""" + +# ********************************************************************************* import os import sys import io @@ -27,25 +28,22 @@ import random from cortix.support.nuclear.fuel_segment import FuelSegment -#********************************************************************************* +# ********************************************************************************* + -class FuelSegmentsGroups(): - ''' +class FuelSegmentsGroups: + """ Creates a dictionary of lists of fuel segment objects, with the keys typically being timestamps. Each fuel segment object has two data members, a `Pandas` Series for geometry spec and a panda DataFrame for property density. - ''' - -#********************************************************************************* -# Construction -#********************************************************************************* + """ - def __init__(self, - key=None, - fuelSegments=None - ): + # ********************************************************************************* + # Construction + # ********************************************************************************* + def __init__(self, key=None, fuelSegments=None): # constructor # FuelSegmentsGroups simply encapsulates a dictionary of a list of # FuelSegment objects. The key is typically a time stamp. @@ -66,13 +64,12 @@ def __init__(self, return -#********************************************************************************* -# Public Member Functions -#********************************************************************************* + # ********************************************************************************* + # Public Member Functions + # ********************************************************************************* def HasGroup(self, key): - - ''' + """ Checks if the specified key has a group of fuel segments associated with it. @@ -83,13 +80,12 @@ def HasGroup(self, key): Returns ------- key: str - ''' + """ return key in self.groups.keys() def AddGroup(self, key, fuelSegments=None): - - ''' + """ Appends the dictionary with a new key and associated list of fuelSegments. If the specified key is already present in the dictionary, then the specified list of fuel segments will be appended @@ -99,16 +95,16 @@ def AddGroup(self, key, fuelSegments=None): ---------- key: str fuelSegments: list - ''' + """ self.__AddGroup(key, fuelSegments) return - def GetAttribute(self, groupKey=None, attributeName=None, - nuclideSymbol=None, nuclideSeries=None): - - ''' + def GetAttribute( + self, groupKey=None, attributeName=None, nuclideSymbol=None, nuclideSeries=None + ): + """ Returns the average value of an attribute amongst all elements in a group (WARNING: keys with no values associated with them will lower this average!). If groupKey is not specified, the function will return @@ -127,14 +123,14 @@ def GetAttribute(self, groupKey=None, attributeName=None, Returns ------- groupAttribute: float - ''' + """ return self.__GetGroupAttribute( - groupKey, attributeName, nuclideSymbol, nuclideSeries) + groupKey, attributeName, nuclideSymbol, nuclideSeries + ) def GetFuelSegments(self, groupKey=None): - - ''' + """ Returns a list of fuel segments associated with a specified groupkey. If no group key is specified, then all elements in the dictionary will be returned. If the specified group key does not exist, then the @@ -147,13 +143,12 @@ def GetFuelSegments(self, groupKey=None): Returns ---------- fuelSegments: list - ''' + """ return self.__GetFuelSegments(groupKey) def RemoveFuelSegment(self, groupKey, fuelSegment): - - ''' + """ Removes a fuel segment from a list associated with a specified group key. If the specified group key or fuel segment do not exist, the function will fail. @@ -166,29 +161,29 @@ def RemoveFuelSegment(self, groupKey, fuelSegment): Returns ------- empty: - ''' + """ return self.__RemoveFuelSegment(groupKey, fuelSegment) -# def __str__( self ): -# s = ' %5s %5s %5s '+' molar mass: %6s '+' molar cc: %6s '+' mass cc: %6s '+' flag: %s '+'# atoms: %s'+' atoms: %s\n' -# return s % (self.name, self.formula, self.phase, self.molarMass, self.molarCC, self.massCC, self.flag, self.nAtoms, self.atoms) -# -# def __repr__( self ): -# s = ' %5s %5s %5s '+' molar mass: %6s '+' molar cc: %6s '+' mass cc: %6s '+' flag: %s '+'# atoms: %s'+' atoms: %s\n' -# return s % (self.name, self.formula, self.phase, self.molarMass, self.molarCC, self.massCC, self.flag, self.nAtoms, self.atoms) - -#********************************************************************************* -# Private helper functions (internal use: __) -#********************************************************************************* - -# If an attibute is not found, Return 0 even if a groupKey is not found -# Don't change this behavior; it will break user's code. - - def __GetGroupAttribute(self, groupKey=None, - attributeName=None, symbol=None, series=None): - - ''' + # def __str__( self ): + # s = ' %5s %5s %5s '+' molar mass: %6s '+' molar cc: %6s '+' mass cc: %6s '+' flag: %s '+'# atoms: %s'+' atoms: %s\n' + # return s % (self.name, self.formula, self.phase, self.molarMass, self.molarCC, self.massCC, self.flag, self.nAtoms, self.atoms) + # + # def __repr__( self ): + # s = ' %5s %5s %5s '+' molar mass: %6s '+' molar cc: %6s '+' mass cc: %6s '+' flag: %s '+'# atoms: %s'+' atoms: %s\n' + # return s % (self.name, self.formula, self.phase, self.molarMass, self.molarCC, self.massCC, self.flag, self.nAtoms, self.atoms) + + # ********************************************************************************* + # Private helper functions (internal use: __) + # ********************************************************************************* + + # If an attibute is not found, Return 0 even if a groupKey is not found + # Don't change this behavior; it will break user's code. + + def __GetGroupAttribute( + self, groupKey=None, attributeName=None, symbol=None, series=None + ): + """ Returns the cumulative or average densities of all fuel segments in all groups. Use this function with discretion, as groups with no segments will reduce the average density value. @@ -203,39 +198,39 @@ def __GetGroupAttribute(self, groupKey=None, Returns ------- attribute: float - ''' + """ - assert attributeName is not None, 'fatal.' + assert attributeName is not None, "fatal." attribute = None if groupKey is None: - attribute = 0 - for (key, fuelSegments) in self.groups.items(): - assert isinstance(fuelSegments, list), 'fail.' + for key, fuelSegments in self.groups.items(): + assert isinstance(fuelSegments, list), "fail." if len(fuelSegments) == 0: continue # this will reduce the average value groupAttribute = 0 for fuelSegment in fuelSegments: groupAttribute += fuelSegment.get_attribute( - attributeName, symbol, series) + attributeName, symbol, series + ) - if attributeName[-4:] == 'Dens' or attributeName[-2:] == 'CC': + if attributeName[-4:] == "Dens" or attributeName[-2:] == "CC": groupAttribute /= len(fuelSegments) attribute += groupAttribute - if attribute != 0 and \ - (attributeName[-4:] == 'Dens' or attributeName[-2:] == 'CC'): + if attribute != 0 and ( + attributeName[-4:] == "Dens" or attributeName[-2:] == "CC" + ): attribute /= len(self.groups) -# if attributeName[-4:] == 'Dens' or attributeName[-2:] == 'CC': -# print('HELLO density '+attributeName+' ', attribute) + # if attributeName[-4:] == 'Dens' or attributeName[-2:] == 'CC': + # print('HELLO density '+attributeName+' ', attribute) # Get average property in all fuel segments within a groupKey else: - if groupKey not in self.groups.keys(): return 0 @@ -247,18 +242,17 @@ def __GetGroupAttribute(self, groupKey=None, attribute = 0 for fuelSegment in fuelSegments: - attribute += fuelSegment.get_attribute( - attributeName, symbol, series) + attribute += fuelSegment.get_attribute(attributeName, symbol, series) - if attribute != 0 and \ - (attributeName[-4:] == 'Dens' or attributeName[-2:] == 'CC'): + if attribute != 0 and ( + attributeName[-4:] == "Dens" or attributeName[-2:] == "CC" + ): attribute /= len(fuelSegments) return attribute def __GetFuelSegments(self, groupKey=None): - - ''' + """ Returns a list of fuel segments associated with a given group (if groupKey is specified), or an ordered list of pairs of all segments in all groups and their keys. [ (timeStamp, fuelSegment), @@ -272,13 +266,12 @@ def __GetFuelSegments(self, groupKey=None): Returns ------- sorted_data: type or self.groups[groupKey]: type or list(): list - ''' + """ if groupKey is None: # return an ordered list of all fuelSegments - tmp = list() timeStamp = list() - for (key, fuelSegments) in self.groups.items(): + for key, fuelSegments in self.groups.items(): if fuelSegments is None: continue tmp += fuelSegments @@ -288,21 +281,18 @@ def __GetFuelSegments(self, groupKey=None): # sort fuelSegments in order of their keys data = zip(timeStamp, tmp) # this is a list of pairs - sorted_data = sorted( - data, key=lambda entry: entry[0], reverse=False) - #tmp = [ y for (x,y) in sorted_data ] # oldest first + sorted_data = sorted(data, key=lambda entry: entry[0], reverse=False) + # tmp = [ y for (x,y) in sorted_data ] # oldest first return sorted_data else: - if groupKey not in self.groups.keys(): return list() return self.groups[groupKey] def __AddGroup(self, groupKey, fuelSegments=None): - - ''' + """ If a list of fuel segments and a group key is specified, the fuelSegment list is appended to the specified groupKey. If the groupKey specified does not already exist, a new one is created and the @@ -313,12 +303,12 @@ def __AddGroup(self, groupKey, fuelSegments=None): ---------- groupKey: str fuelSegments: list - ''' + """ if fuelSegments is None: fuelSegments = list() else: - assert isinstance(fuelSegments, list), 'fail.' + assert isinstance(fuelSegments, list), "fail." if groupKey in self.groups.keys(): self.groups[groupKey] += fuelSegments @@ -328,8 +318,7 @@ def __AddGroup(self, groupKey, fuelSegments=None): return def __RemoveFuelSegment(self, groupKey, fuelSegment_remove): - - ''' + """ Removes a fuel segment from a list associated with a specified group key. If the specified group key or fuel segment do not exist, the function will fail. @@ -338,20 +327,22 @@ def __RemoveFuelSegment(self, groupKey, fuelSegment_remove): ---------- groupKey: str fuelSegment: str - ''' + """ - assert groupKey in self.groups.keys(), 'fail.' + assert groupKey in self.groups.keys(), "fail." fuelSegments = self.groups[groupKey] nSegments = len(fuelSegments) for fuelSegment in fuelSegments: if fuelSegment.get_attribute( - 'segmentId') == fuelSegment_remove.get_attribute('segmentId'): + "segmentId" + ) == fuelSegment_remove.get_attribute("segmentId"): fuelSegments.remove(fuelSegment) - assert len(self.groups[groupKey]) == nSegments - 1, 'fatal.' + assert len(self.groups[groupKey]) == nSegments - 1, "fatal." return -#========================= end class FuelSegmentsGroups ========================== + +# ========================= end class FuelSegmentsGroups ========================== diff --git a/cortix/support/nuclear/fuelslug.py b/cortix/support/nuclear/fuelslug.py index 4d8ea15c..5d2c2f0a 100644 --- a/cortix/support/nuclear/fuelslug.py +++ b/cortix/support/nuclear/fuelslug.py @@ -8,7 +8,7 @@ # # Licensed under the University of Massachusetts Lowell LICENSE: # https://github.com/dpploy/cortix/blob/master/LICENSE.txt -''' +""" Author: Valmor de Almeida dealmeidav@ornl.gov; vfda Fuel slug @@ -24,7 +24,8 @@ VFdALib support classes Thu Dec 15 16:18:39 EST 2016 -''' +""" + # ******************************************************************************* import os import sys @@ -41,53 +42,49 @@ from cortix.support.phase import Phase # ******************************************************************************* -class FuelSlug: -#********************************************************************************* -# Construction -#********************************************************************************* - - def __init__(self, - specs = pandas.Series(), - fuelPhase = Phase(), - claddingPhase = Phase() - ): - - assert isinstance(specs, pandas.Series), 'fatal.' - assert isinstance(fuelPhase, Phase), 'fatal.' - assert isinstance(claddingPhase, Phase), 'fatal.' - - self.attributeNames = \ - ['nSlugs', - 'slugType', - 'slugVolume', - 'slugArea', - 'fuelVolume', - 'claddingVolume', - 'fuelArea', - 'claddingArea', - 'equivalentCladdingVolume', - 'equivalentCladdingArea', - 'equivalentFuelVolume', - 'equivalentFuelArea', - 'fuelLength', - 'slugLength', - 'fuelMass', - 'fuelMassDens', - 'fuelMassCC', - 'claddingMass', - 'claddingMassDens', - 'claddingMassCC', - 'nuclides', - 'isotopes', - 'radioactivity', - 'radioactivityDens', - 'gamma', - 'gammaDens', - 'heat', - 'heatDens', - 'molarHeatPwr', - 'molarGammaPwr'] +class FuelSlug: + # ********************************************************************************* + # Construction + # ********************************************************************************* + + def __init__(self, specs=pandas.Series(), fuelPhase=Phase(), claddingPhase=Phase()): + assert isinstance(specs, pandas.Series), "fatal." + assert isinstance(fuelPhase, Phase), "fatal." + assert isinstance(claddingPhase, Phase), "fatal." + + self.attributeNames = [ + "nSlugs", + "slugType", + "slugVolume", + "slugArea", + "fuelVolume", + "claddingVolume", + "fuelArea", + "claddingArea", + "equivalentCladdingVolume", + "equivalentCladdingArea", + "equivalentFuelVolume", + "equivalentFuelArea", + "fuelLength", + "slugLength", + "fuelMass", + "fuelMassDens", + "fuelMassCC", + "claddingMass", + "claddingMassDens", + "claddingMassCC", + "nuclides", + "isotopes", + "radioactivity", + "radioactivityDens", + "gamma", + "gammaDens", + "heat", + "heatDens", + "molarHeatPwr", + "molarGammaPwr", + ] # own internal copy self._specs = deepcopy(specs) @@ -97,68 +94,71 @@ def __init__(self, # setup the equivalent cladding hollow sphere pi = math.pi - area = self.__GetAttribute('claddingArea') - volume = self.__GetAttribute('claddingVolume') + area = self.__GetAttribute("claddingArea") + volume = self.__GetAttribute("claddingVolume") ro = math.sqrt(area / 4 / pi) - ri = (ro**3 - volume * 3 / 4 / pi)**(1 / 3) + ri = (ro**3 - volume * 3 / 4 / pi) ** (1 / 3) self._claddingHollowSphereRo = ro self._claddingHollowSphereRi = ri - area = self.__GetAttribute('fuelArea') - volume = self.__GetAttribute('fuelVolume') + area = self.__GetAttribute("fuelArea") + volume = self.__GetAttribute("fuelVolume") ro = math.sqrt(area / 4 / pi) - ri = (ro**3 - volume * 3 / 4 / pi)**(1 / 3) + ri = (ro**3 - volume * 3 / 4 / pi) ** (1 / 3) self._fuelHollowSphereRo = ro self._fuelHollowSphereRi = ri return -#********************************************************************************* -# Public Member Functions -#********************************************************************************* + # ********************************************************************************* + # Public Member Functions + # ********************************************************************************* def GetSpecs(self): - ''' + """ Returns the species associated with this fuel slug. Returns ------- specs: str - ''' + """ return self._specs + specs = property(GetSpecs, None, None, None) def GetFuelPhase(self): - ''' + """ Returns the phase history of the solid fuel. Returns ------- fuelPhase: dataFrame - ''' + """ return self._fuelPhase + fuelPhase = property(GetFuelPhase, None, None, None) def GetCladdingPhase(self): - ''' + """ Returns the phase history of the cladding. Returns ------- claddingPhase: dataFrame - ''' + """ return self._claddingPhase + claddingPhase = property(GetCladdingPhase, None, None, None) def GetAttribute(self, name, phase=None, symbol=None, series=None): - ''' + """ Returns the value of the specified attribute. Any attribute that is specified in class construction can be retrieved using this function. The attribute may also be retrived from a speciefic phase, a specific @@ -174,12 +174,12 @@ def GetAttribute(self, name, phase=None, symbol=None, series=None): Returns ------- attribute: int or float - ''' + """ return self.__GetAttribute(name, symbol, series) def ReduceCladdingVolume(self, dissolvedVolume): - ''' + """ Reduces the amount of cladding in the slug by dissolvedvolume. This will also update the dimensions of the cladding walls and end caps; volume will be taken from all sections equally such that the @@ -188,12 +188,12 @@ def ReduceCladdingVolume(self, dissolvedVolume): Parameters ---------- dissolvedVolume: float - ''' + """ self.__ReduceCladdingVolume(dissolvedVolume) def ReduceFuelVolume(self, dissolvedVolume): - ''' + """ Reduces the amount of fuel in the slug by dissolvedVolume. This will also update the dimensions of the fuel slug, mainly the thickness of each fuel layers. @@ -201,26 +201,26 @@ def ReduceFuelVolume(self, dissolvedVolume): Parameters ---------- dissolvedVolume: float - ''' + """ self.__ReduceFuelVolume(dissolvedVolume) def __str__(self): - s = 'FuelSlug(): \n\t specs \n\t %s \n\t fuelPhase \n\t %s \n\t claddingPhase \n\t %s' + s = "FuelSlug(): \n\t specs \n\t %s \n\t fuelPhase \n\t %s \n\t claddingPhase \n\t %s" return s % (self._specs, self._fuelPhase, self._claddingPhase) def __repr__(self): - s = 'FuelSlug(): \n\t specs \n\t %s \n\t fuelPhase \n\t %s \n\t claddingPhase \n\t %s' + s = "FuelSlug(): \n\t specs \n\t %s \n\t fuelPhase \n\t %s \n\t claddingPhase \n\t %s" return s % (self._specs, self._fuelPhase, self._claddingPhase) -# Get stored fuel slug property either overall or on a nuclide basis + # Get stored fuel slug property either overall or on a nuclide basis -#********************************************************************************* -# Private Member Functions (Internal use: __) -#********************************************************************************* + # ********************************************************************************* + # Private Member Functions (Internal use: __) + # ********************************************************************************* def __GetAttribute(self, attributeName, nuclide=None, series=None): - ''' + """ Returns the specified attribute. Series and nuclide must both be specified if the other is specified! @@ -233,281 +233,299 @@ def __GetAttribute(self, attributeName, nuclide=None, series=None): Returns ------- Various attributes. - ''' + """ - - assert attributeName in self.attributeNames, ' attribute name: %r; options: %r; fail.' % ( - attributeName, self.attributeNames) + assert attributeName in self.attributeNames, ( + " attribute name: %r; options: %r; fail." + % (attributeName, self.attributeNames) + ) if nuclide is not None: - assert len(nuclide.split('*')) == 1 # sanity check + assert len(nuclide.split("*")) == 1 # sanity check if nuclide is not None: - assert series is None, 'fail.' + assert series is None, "fail." if series is not None: - assert nuclide is None, 'fail.' + assert nuclide is None, "fail." if series is not None: - assert False, ' not implemented.' + assert False, " not implemented." - if attributeName == 'isotopes': - assert nuclide is not None, 'need a nuclide symbol.' + if attributeName == "isotopes": + assert nuclide is not None, "need a nuclide symbol." -# ................................................................................. -# # of slugs + # ................................................................................. + # # of slugs - if attributeName == 'nSlugs': + if attributeName == "nSlugs": return 1 -# ................................................................................. -# slugId + # ................................................................................. + # slugId - if attributeName == 'slugType': - return self._specs['Slug type'] + if attributeName == "slugType": + return self._specs["Slug type"] -# ................................................................................. -# slug volume + # ................................................................................. + # slug volume - if attributeName == 'slugVolume': + if attributeName == "slugVolume": return __GetSlugVolume(self) -# ................................................................................. -# fuel volume + # ................................................................................. + # fuel volume - if attributeName == 'fuelVolume': + if attributeName == "fuelVolume": return __GetFuelVolume(self) -# ................................................................................. -# equivalent fuel volume + # ................................................................................. + # equivalent fuel volume - if attributeName == 'equivalentFuelVolume': + if attributeName == "equivalentFuelVolume": return __GetEquivalentFuelVolume(self) -# ................................................................................. -# cladding volume + # ................................................................................. + # cladding volume - if attributeName == 'claddingVolume': + if attributeName == "claddingVolume": return __GetCladdingVolume(self) -# ................................................................................. -# equivalent cladding volume + # ................................................................................. + # equivalent cladding volume - if attributeName == 'equivalentCladdingVolume': + if attributeName == "equivalentCladdingVolume": return __GetEquivalentCladdingVolume(self) -# ................................................................................. -# fuel area + # ................................................................................. + # fuel area - if attributeName == 'fuelArea': + if attributeName == "fuelArea": return __GetFuelArea(self) -# ................................................................................. -# equivalent fuel area + # ................................................................................. + # equivalent fuel area - if attributeName == 'equivalentFuelArea': + if attributeName == "equivalentFuelArea": return __GetEquivalentFuelArea(self) -# ................................................................................. -# cladding area + # ................................................................................. + # cladding area - if attributeName == 'claddingArea': + if attributeName == "claddingArea": return __GetCladdingArea(self) - if attributeName == 'slugArea': + if attributeName == "slugArea": return __GetCladdingArea(self) -# ................................................................................. -# equivalent cladding area + # ................................................................................. + # equivalent cladding area - if attributeName == 'equivalentCladdingArea': + if attributeName == "equivalentCladdingArea": return __GetEquivalentCladdingArea(self) -# ................................................................................. -# fuel length + # ................................................................................. + # fuel length - if attributeName == 'fuelLength': + if attributeName == "fuelLength": return __GetFuelLength(self) -# ................................................................................. -# fuel slug overall quantities + # ................................................................................. + # fuel slug overall quantities if nuclide is None and series is None: - # mass or mass concentration - if attributeName == 'fuelMassCC' or attributeName == 'fuelMassDens' or attributeName == 'fuelMass': - mass = self._fuelPhase.GetValue('mass') - if attributeName == 'fuelMass': + if ( + attributeName == "fuelMassCC" + or attributeName == "fuelMassDens" + or attributeName == "fuelMass" + ): + mass = self._fuelPhase.GetValue("mass") + if attributeName == "fuelMass": return mass else: # volume = __GetFuelVolume( self ) volume = __GetEquivalentFuelVolume(self) if volume == 0.0: - assert abs( - volume - self._fuelPhase.GetValue('volume')) < 1e-8 + assert abs(volume - self._fuelPhase.GetValue("volume")) < 1e-8 return mass else: - assert abs( - volume - self._fuelPhase.GetValue('volume')) / volume * 100.0 < 0.1 + assert ( + abs(volume - self._fuelPhase.GetValue("volume")) + / volume + * 100.0 + < 0.1 + ) return mass / volume -# mass or mass concentration - if attributeName == 'claddingMassCC' or attributeName == 'claddingMassDens' or attributeName == 'claddingMass': - mass = self._claddingPhase.GetValue('mass') - if attributeName == 'claddingMass': + # mass or mass concentration + if ( + attributeName == "claddingMassCC" + or attributeName == "claddingMassDens" + or attributeName == "claddingMass" + ): + mass = self._claddingPhase.GetValue("mass") + if attributeName == "claddingMass": return mass else: # volume = __GetCladdingVolume( self ) volume = __GetEquivalentCladdingVolume(self) if volume == 0.0: - assert abs( - volume - self._claddingPhase.GetValue('volume')) < 1e-8 + assert ( + abs(volume - self._claddingPhase.GetValue("volume")) < 1e-8 + ) return mass else: - assert abs( - volume - self._claddingPhase.GetValue('volume')) / volume * 100.0 < 0.1 + assert ( + abs(volume - self._claddingPhase.GetValue("volume")) + / volume + * 100.0 + < 0.1 + ) return mass / volume -# radioactivity - if attributeName == 'radioactivtyDens' or attributeName == 'radioactivity': - radioactivity = self._fuelPhase.GetValue('radioactivity') - if attributeName == 'radioactivity': + # radioactivity + if attributeName == "radioactivtyDens" or attributeName == "radioactivity": + radioactivity = self._fuelPhase.GetValue("radioactivity") + if attributeName == "radioactivity": return radioactivity else: volume = __GetFuelVolume(self) if volume == 0.0: - assert abs( - volume - self._fuelPhase.GetValue('volume')) < 1e-8 + assert abs(volume - self._fuelPhase.GetValue("volume")) < 1e-8 return radioactivity else: - assert abs( - volume - self._fuelPhase.GetValue('volume')) / volume * 100.0 < 0.1 + assert ( + abs(volume - self._fuelPhase.GetValue("volume")) + / volume + * 100.0 + < 0.1 + ) return radioactivity / volume -# gamma - if attributeName == 'gammaDens' or attributeName == 'gamma': + # gamma + if attributeName == "gammaDens" or attributeName == "gamma": gammaDens = 0.0 for spc in self._fuelPhase.species: gammaDens += spc.molarGammaPwr * spc.molarCC - if attributeName == 'gammaDens': + if attributeName == "gammaDens": return gammaDens else: volume = __GetFuelVolume(self) return gammaDens * volume -# heat - if attributeName == 'heatDens' or attributeName == 'heat': + # heat + if attributeName == "heatDens" or attributeName == "heat": heatDens = 0.0 for spc in self._fuelPhase.species: heatDens += spc.molarHeatPwr * spc.molarCC - if attributeName == 'heatDens': + if attributeName == "heatDens": return heatDens else: volume = __GetFuelVolume(self) return heatDens * volume -# ................................................................................. -# radioactivity + # ................................................................................. + # radioactivity - if attributeName == 'radioactivityDens' or attributeName == 'radioactivity': + if attributeName == "radioactivityDens" or attributeName == "radioactivity": assert False - colName = 'Radioactivity Dens. [Ci/cc]' + colName = "Radioactivity Dens. [Ci/cc]" -# ................................................................................. -# thermal + # ................................................................................. + # thermal - if attributeName == 'thermalDens' or attributeName == 'thermal' or \ - attributeName == 'heatDens' or attributeName == 'heat': + if ( + attributeName == "thermalDens" + or attributeName == "thermal" + or attributeName == "heatDens" + or attributeName == "heat" + ): assert False - colName = 'Thermal Dens. [W/cc]' + colName = "Thermal Dens. [W/cc]" -# ................................................................................. -# gamma + # ................................................................................. + # gamma - if attributeName == 'gammaDens' or attributeName == 'gamma': + if attributeName == "gammaDens" or attributeName == "gamma": assert False - colName = 'Gamma Dens. [W/cc]' - -# ................................................................................. -########################################################################## -# ................................................................................. - -# if attributeName[-4:] == 'Dens' or attributeName[-2:] == 'CC': -# attributeDens = True -# else: -# attributeDens = False - -# ................................................................................. -# all nuclide content of the fuel added - -# if nuclide is None and series is None: -# -# density = 0.0 -# -# density = self.propertyDensities[ colName ].sum() -# -# if attributeDens is False: -# volume = __GetFuelSlugVolume( self ) -# prop = density * volume -# return prop -# else: -# return density - -# ................................................................................. -# get chemical element series - -# if series is not None: -# -# density = 0.0 -# -# for isotope in isotopes: -# density += self.propertyDensities.loc[isotope,colName] -# -# if attributeDens is False: -# volume = __GetFuelSlugVolume( self ) -# prop = density * volume -# return prop -# else: -# return density - -# ................................................................................. -# get specific nuclide (either the isotopes of the nuclide or the specific -# isotope) property + colName = "Gamma Dens. [W/cc]" + + # ................................................................................. + ########################################################################## + # ................................................................................. + + # if attributeName[-4:] == 'Dens' or attributeName[-2:] == 'CC': + # attributeDens = True + # else: + # attributeDens = False + + # ................................................................................. + # all nuclide content of the fuel added + + # if nuclide is None and series is None: + # + # density = 0.0 + # + # density = self.propertyDensities[ colName ].sum() + # + # if attributeDens is False: + # volume = __GetFuelSlugVolume( self ) + # prop = density * volume + # return prop + # else: + # return density + + # ................................................................................. + # get chemical element series + + # if series is not None: + # + # density = 0.0 + # + # for isotope in isotopes: + # density += self.propertyDensities.loc[isotope,colName] + # + # if attributeDens is False: + # volume = __GetFuelSlugVolume( self ) + # prop = density * volume + # return prop + # else: + # return density + + # ................................................................................. + # get specific nuclide (either the isotopes of the nuclide or the specific + # isotope) property if nuclide is not None: - # a particular nuclide given (atomic number and atomic mass number) - if len(nuclide.split('-')) == 2: - - nuclideMassNumber = int(nuclide.split('-')[1].strip('m')) - nuclideSymbol = nuclide.split('-')[0] - nuclideMolarMass = ELEMENTS[nuclideSymbol].isotopes[nuclideMassNumber].mass + if len(nuclide.split("-")) == 2: + nuclideMassNumber = int(nuclide.split("-")[1].strip("m")) + nuclideSymbol = nuclide.split("-")[0] + nuclideMolarMass = ( + ELEMENTS[nuclideSymbol].isotopes[nuclideMassNumber].mass + ) massCC = 0.0 for spc in self._fuelPhase.species: - formula = spc.atoms moleFraction = 0.0 for item in formula: - - if len(item.split('*') - ) == 1: # no multiplier (implies 1.0) - - formulaNuclideSymbol = item.split('-')[0].strip() + if len(item.split("*")) == 1: # no multiplier (implies 1.0) + formulaNuclideSymbol = item.split("-")[0].strip() if formulaNuclideSymbol == nuclideSymbol: - assert len(item.split('-')) == 2 + assert len(item.split("-")) == 2 - if item.split('*')[0].strip() == nuclide: + if item.split("*")[0].strip() == nuclide: moleFraction = 1.0 else: moleFraction = 0.0 - elif len(item.split('*')) == 2: # with multiplier - - formulaNuclideSymbol = item.split( - '*')[1].split('-')[0].strip() + elif len(item.split("*")) == 2: # with multiplier + formulaNuclideSymbol = ( + item.split("*")[1].split("-")[0].strip() + ) if formulaNuclideSymbol == nuclideSymbol: - assert len(item.split('*')[1].split('-')) == 2 + assert len(item.split("*")[1].split("-")) == 2 - if item.split('*')[1].strip() == nuclide: - moleFraction = float( - item.split('*')[0].strip()) + if item.split("*")[1].strip() == nuclide: + moleFraction = float(item.split("*")[0].strip()) else: moleFraction = 0.0 @@ -518,45 +536,49 @@ def __GetAttribute(self, attributeName, nuclide=None, series=None): return massCC * __GetFuelVolume(self) - # chemical element given (only atomic number given) - elif len(nuclide.split('-')) == 1: - + # chemical element given (only atomic number given) + elif len(nuclide.split("-")) == 1: massCC = 0.0 for spc in self._fuelPhase.species: - formula = spc.atoms for item in formula: - moleFraction = 0.0 - if len(item.split('*') - ) == 1: # no multiplier (implies 1.0) - - assert len(item.split('-')) == 2 - formulaNuclideSymbol = item.split('-')[0].strip() + if len(item.split("*")) == 1: # no multiplier (implies 1.0) + assert len(item.split("-")) == 2 + formulaNuclideSymbol = item.split("-")[0].strip() formulaNuclideMassNumber = int( - item.split('-')[1].strip('m')) - formulaNuclideMolarMass = ELEMENTS[formulaNuclideSymbol].isotopes[formulaNuclideMassNumber].mass + item.split("-")[1].strip("m") + ) + formulaNuclideMolarMass = ( + ELEMENTS[formulaNuclideSymbol] + .isotopes[formulaNuclideMassNumber] + .mass + ) if formulaNuclideSymbol == nuclide: moleFraction = 1.0 else: moleFraction = 0.0 - elif len(item.split('*')) == 2: # with multiplier - - assert len(item.split('*')[1].split('-')) == 2 - formulaNuclideSymbol = item.split( - '*')[1].split('-')[0].strip() + elif len(item.split("*")) == 2: # with multiplier + assert len(item.split("*")[1].split("-")) == 2 + formulaNuclideSymbol = ( + item.split("*")[1].split("-")[0].strip() + ) formulaNuclideMassNumber = int( - item.split('*')[1].split('-')[1].strip('m')) - formulaNuclideMolarMass = ELEMENTS[formulaNuclideSymbol].isotopes[formulaNuclideMassNumber].mass + item.split("*")[1].split("-")[1].strip("m") + ) + formulaNuclideMolarMass = ( + ELEMENTS[formulaNuclideSymbol] + .isotopes[formulaNuclideMassNumber] + .mass + ) if formulaNuclideSymbol == nuclide: - moleFraction = float( - item.split('*')[0].strip()) + moleFraction = float(item.split("*")[0].strip()) else: moleFraction = 0.0 @@ -568,99 +590,106 @@ def __GetAttribute(self, attributeName, nuclide=None, series=None): return massCC * __GetFuelVolume(self) else: - assert False def __GetSlugLength(self): - ''' + """ Returns the length of the fuelslug in cm (includes the end caps). Returns ------- self._specs['Slug length [cm]']: float - ''' + """ - return self._specs['Slug length [cm]'] + return self._specs["Slug length [cm]"] def __GetFuelLength(self): - ''' + """ Returns the length of the fuel section in the slug (this is the length of the fuel slug without the cladding end caps). Given in cm. Returns ------- fuelLength: float - ''' + """ slugLength = __GetSlugLength(self) - cladEndCapThickness = self._specs['Cladding end cap thickness [cm]'] + cladEndCapThickness = self._specs["Cladding end cap thickness [cm]"] fuelLength = slugLength - 2.0 * cladEndCapThickness return fuelLength def __GetSlugVolume(self): - ''' + """ Returns the total volume of the fuel slug, in cm^3. Returns ------- outerSlugVolume + innerSlugVolume: float - ''' + """ slugLength = __GetSlugLength(self) - outerSlugOuterRadius = self._specs['Outer slug OD [cm]'] / 2.0 - outerSlugInnerRadius = self._specs['Outer slug ID [cm]'] / 2.0 - outerSlugVolume = slugLength * math.pi * \ - (outerSlugOuterRadius**2 - outerSlugInnerRadius**2) - innerSlugOuterRadius = self._specs['Inner slug OD [cm]'] / 2.0 - innerSlugInnerRadius = self._specs['Inner slug ID [cm]'] / 2.0 - innerSlugVolume = slugLength * math.pi * \ - (innerSlugOuterRadius**2 - innerSlugInnerRadius**2) + outerSlugOuterRadius = self._specs["Outer slug OD [cm]"] / 2.0 + outerSlugInnerRadius = self._specs["Outer slug ID [cm]"] / 2.0 + outerSlugVolume = ( + slugLength * math.pi * (outerSlugOuterRadius**2 - outerSlugInnerRadius**2) + ) + innerSlugOuterRadius = self._specs["Inner slug OD [cm]"] / 2.0 + innerSlugInnerRadius = self._specs["Inner slug ID [cm]"] / 2.0 + innerSlugVolume = ( + slugLength * math.pi * (innerSlugOuterRadius**2 - innerSlugInnerRadius**2) + ) return outerSlugVolume + innerSlugVolume def __GetFuelVolume(self): - ''' + """ Returns the total volume of fuel in the slug, in cm^3. Returns ------- volume: float - ''' + """ fuelLength = __GetFuelLength(self) - cladWallThickness = self._specs['Cladding wall thickness [cm]'] - outerFuelOuterRadius = self._specs['Outer slug OD [cm]'] / \ - 2.0 - cladWallThickness - outerFuelInnerRadius = self._specs['Outer slug ID [cm]'] / \ - 2.0 + cladWallThickness - - outerFuelVolume = fuelLength * math.pi * \ - (outerFuelOuterRadius**2 - outerFuelInnerRadius**2) - - innerFuelOuterRadius = self._specs['Inner slug OD [cm]'] / \ - 2.0 - cladWallThickness - innerFuelInnerRadius = self._specs['Inner slug ID [cm]'] / \ - 2.0 + cladWallThickness - - innerFuelVolume = fuelLength * math.pi * \ - (innerFuelOuterRadius**2 - innerFuelInnerRadius**2) + cladWallThickness = self._specs["Cladding wall thickness [cm]"] + outerFuelOuterRadius = ( + self._specs["Outer slug OD [cm]"] / 2.0 - cladWallThickness + ) + outerFuelInnerRadius = ( + self._specs["Outer slug ID [cm]"] / 2.0 + cladWallThickness + ) + + outerFuelVolume = ( + fuelLength * math.pi * (outerFuelOuterRadius**2 - outerFuelInnerRadius**2) + ) + + innerFuelOuterRadius = ( + self._specs["Inner slug OD [cm]"] / 2.0 - cladWallThickness + ) + innerFuelInnerRadius = ( + self._specs["Inner slug ID [cm]"] / 2.0 + cladWallThickness + ) + + innerFuelVolume = ( + fuelLength * math.pi * (innerFuelOuterRadius**2 - innerFuelInnerRadius**2) + ) volume = outerFuelVolume + innerFuelVolume return volume def __GetCladdingVolume(self): - ''' + """ Returns the total volume of cladding in the slug. Returns ------- slugVolume - fuelVolume: float - ''' + """ slugVolume = __GetSlugVolume(self) fuelVolume = __GetFuelVolume(self) @@ -668,86 +697,102 @@ def __GetCladdingVolume(self): return slugVolume - fuelVolume def __GetFuelArea(self): - ''' + """ Returns the surface area of the fuel, in cm^2. Returns ------- outerSlugFuelArea + innerSlugFuelArea: float - ''' + """ pi = math.pi - cladWallThickness = self._specs['Cladding wall thickness [cm]'] - slugLength = self._specs['Slug length [cm]'] + cladWallThickness = self._specs["Cladding wall thickness [cm]"] + slugLength = self._specs["Slug length [cm]"] outerSlugFuelArea = 0.0 # side walls - outerSlugOuterRadius = self._specs['Outer slug OD [cm]'] / 2.0 - outerSlugFuelArea += 2.0 * pi * \ - (outerSlugOuterRadius - cladWallThickness) * slugLength - outerSlugInnerRadius = self._specs['Outer slug ID [cm]'] / 2.0 - outerSlugFuelArea += 2.0 * pi * \ - (outerSlugInnerRadius + cladWallThickness) * slugLength + outerSlugOuterRadius = self._specs["Outer slug OD [cm]"] / 2.0 + outerSlugFuelArea += ( + 2.0 * pi * (outerSlugOuterRadius - cladWallThickness) * slugLength + ) + outerSlugInnerRadius = self._specs["Outer slug ID [cm]"] / 2.0 + outerSlugFuelArea += ( + 2.0 * pi * (outerSlugInnerRadius + cladWallThickness) * slugLength + ) # add bottom and top areas - outerSlugFuelArea += 2.0 * pi * \ - ((outerSlugOuterRadius - cladWallThickness)**2 - - (outerSlugInnerRadius + cladWallThickness)**2) + outerSlugFuelArea += ( + 2.0 + * pi + * ( + (outerSlugOuterRadius - cladWallThickness) ** 2 + - (outerSlugInnerRadius + cladWallThickness) ** 2 + ) + ) innerSlugFuelArea = 0.0 # side walls - innerSlugOuterRadius = self._specs['Inner slug OD [cm]'] / 2.0 - innerSlugFuelArea += 2.0 * pi * \ - (innerSlugOuterRadius - cladWallThickness) * slugLength - innerSlugInnerRadius = self._specs['Inner slug ID [cm]'] / 2.0 - innerSlugFuelArea += 2.0 * pi * \ - (innerSlugInnerRadius + cladWallThickness) * slugLength + innerSlugOuterRadius = self._specs["Inner slug OD [cm]"] / 2.0 + innerSlugFuelArea += ( + 2.0 * pi * (innerSlugOuterRadius - cladWallThickness) * slugLength + ) + innerSlugInnerRadius = self._specs["Inner slug ID [cm]"] / 2.0 + innerSlugFuelArea += ( + 2.0 * pi * (innerSlugInnerRadius + cladWallThickness) * slugLength + ) # add bottom and top areas - innerSlugFuelArea += 2.0 * pi * \ - ((innerSlugOuterRadius - cladWallThickness)**2 - - (innerSlugInnerRadius + cladWallThickness)**2) + innerSlugFuelArea += ( + 2.0 + * pi + * ( + (innerSlugOuterRadius - cladWallThickness) ** 2 + - (innerSlugInnerRadius + cladWallThickness) ** 2 + ) + ) return outerSlugFuelArea + innerSlugFuelArea def __GetCladdingArea(self): - ''' + """ Returns the surface area of the cladding, in cm^2. Returns ------- outerSlugArea + innerSlugArea: float - ''' + """ - slugLength = self._specs['Slug length [cm]'] + slugLength = self._specs["Slug length [cm]"] outerSlugArea = 0.0 # side walls - outerSlugOuterRadius = self._specs['Outer slug OD [cm]'] / 2.0 + outerSlugOuterRadius = self._specs["Outer slug OD [cm]"] / 2.0 outerSlugArea += 2.0 * math.pi * outerSlugOuterRadius * slugLength - outerSlugInnerRadius = self._specs['Outer slug ID [cm]'] / 2.0 + outerSlugInnerRadius = self._specs["Outer slug ID [cm]"] / 2.0 outerSlugArea += 2.0 * math.pi * outerSlugInnerRadius * slugLength # add bottom and top areas - outerSlugArea += 2.0 * math.pi * \ - (outerSlugOuterRadius**2 - outerSlugInnerRadius**2) + outerSlugArea += ( + 2.0 * math.pi * (outerSlugOuterRadius**2 - outerSlugInnerRadius**2) + ) innerSlugArea = 0.0 # side walls - innerSlugOuterRadius = self._specs['Inner slug OD [cm]'] / 2.0 + innerSlugOuterRadius = self._specs["Inner slug OD [cm]"] / 2.0 innerSlugArea += 2.0 * math.pi * innerSlugOuterRadius * slugLength - innerSlugInnerRadius = self._specs['Inner slug ID [cm]'] / 2.0 + innerSlugInnerRadius = self._specs["Inner slug ID [cm]"] / 2.0 innerSlugArea += 2.0 * math.pi * innerSlugInnerRadius * slugLength # add bottom and top areas - innerSlugArea += 2.0 * math.pi * \ - (innerSlugOuterRadius**2 - innerSlugInnerRadius**2) + innerSlugArea += ( + 2.0 * math.pi * (innerSlugOuterRadius**2 - innerSlugInnerRadius**2) + ) return outerSlugArea + innerSlugArea def __GetEquivalentCladdingArea(self): - ''' + """ Returns the surface area of a sphere with a radius given by self._claddingHollowSphereRo with the same surface area as the cladding on the fuel slug, in cm^2. @@ -755,7 +800,7 @@ def __GetEquivalentCladdingArea(self): Returns ------- area: float - ''' + """ ro = self._claddingHollowSphereRo @@ -764,14 +809,14 @@ def __GetEquivalentCladdingArea(self): return area def __GetEquivalentCladdingVolume(self): - ''' + """ Returns the equivalent cladding volume of a hollow sphere that is equal to the cladding volume in the fuel slug, in cm^3. Returns ------- volume: float - ''' + """ ro = self._claddingHollowSphereRo ri = self._claddingHollowSphereRi @@ -781,14 +826,14 @@ def __GetEquivalentCladdingVolume(self): return volume def __GetEquivalentFuelVolume(self): - ''' + """ This function does the same thing as __GetEquivalentCladdingVolume but for fuel instead of clading. Returns ------- volume: float - ''' + """ ro = self._fuelHollowSphereRo ri = self._fuelHollowSphereRi @@ -798,14 +843,14 @@ def __GetEquivalentFuelVolume(self): return volume def __GetEquivalentFuelArea(self): - ''' + """ This function does the same thing as __GetEquiavelntCladdingArea, but for fuel instead of cladding. Returns ------- area: float - ''' + """ ro = self._fuelHollowSphereRo @@ -813,10 +858,10 @@ def __GetEquivalentFuelArea(self): return area -# Shrink the volume based on the equivalent cladding hollow sphere + # Shrink the volume based on the equivalent cladding hollow sphere def __ReduceCladdingVolume(self, dissolvedVolume): - ''' + """ Reduces the volume of cladding by the specified amount and then shrinks the dimensions of the cladding walls to fit the new volume. The function does this by first creating a hollow sphere with the same @@ -827,10 +872,11 @@ def __ReduceCladdingVolume(self, dissolvedVolume): Parameters ---------- dissolvedVolume: float - ''' + """ - assert dissolvedVolume >= 0.0, 'dissolved volume= %r; failed.' % ( - dissolvedVolume) + assert dissolvedVolume >= 0.0, "dissolved volume= %r; failed." % ( + dissolvedVolume + ) if dissolvedVolume == 0.0: return @@ -842,10 +888,10 @@ def __ReduceCladdingVolume(self, dissolvedVolume): pi = math.pi # get this first - massDens = _GetAttribute(self, 'claddingMassDens') + massDens = _GetAttribute(self, "claddingMassDens") -# ................................................................................. -# reduce the volume of the cladding hollow sphere + # ................................................................................. + # reduce the volume of the cladding hollow sphere ro = self._claddingHollowSphereRo ri = self._claddingHollowSphereRi @@ -853,47 +899,46 @@ def __ReduceCladdingVolume(self, dissolvedVolume): volume = 4.0 / 3.0 * pi * (ro**3 - ri**3) if dV < volume: - - ro = (ri**3 + 3.0 / 4.0 / pi * (volume - dV))**(1 / 3) + ro = (ri**3 + 3.0 / 4.0 / pi * (volume - dV)) ** (1 / 3) self._claddingHollowSphereRo = ro else: - self._claddingHollowSphereRo = 0.0 self._claddingHollowSphereRi = 0.0 - cladWallThickness = self._specs['Cladding wall thickness [cm]'] - cladEndCapThickness = self._specs['Cladding end cap thickness [cm]'] + cladWallThickness = self._specs["Cladding wall thickness [cm]"] + cladEndCapThickness = self._specs["Cladding end cap thickness [cm]"] - self._specs['Inner slug ID [cm]'] += cladWallThickness - self._specs['Inner slug OD [cm]'] -= cladWallThickness - self._specs['Outer slug ID [cm]'] += cladWallThickness - self._specs['Outer slug OD [cm]'] -= cladWallThickness + self._specs["Inner slug ID [cm]"] += cladWallThickness + self._specs["Inner slug OD [cm]"] -= cladWallThickness + self._specs["Outer slug ID [cm]"] += cladWallThickness + self._specs["Outer slug OD [cm]"] -= cladWallThickness - self._specs['Slug length [cm]'] -= 2.0 * cladEndCapThickness + self._specs["Slug length [cm]"] -= 2.0 * cladEndCapThickness - self._specs['Cladding wall thickness [cm]'] = 0.0 - self._specs['Cladding end cap thickness [cm]'] = 0.0 + self._specs["Cladding wall thickness [cm]"] = 0.0 + self._specs["Cladding end cap thickness [cm]"] = 0.0 -# ................................................................................. -# Update the history of the cladding phase + # ................................................................................. + # Update the history of the cladding phase - volume = _GetAttribute(self, 'equivalentCladdingVolume') - self._claddingPhase.SetValue('volume', volume) + volume = _GetAttribute(self, "equivalentCladdingVolume") + self._claddingPhase.SetValue("volume", volume) - self._claddingPhase.SetValue('mass', massDens * volume) + self._claddingPhase.SetValue("mass", massDens * volume) def __ReduceFuelVolume(self, dissolvedVolume): - ''' + """ See __ReduceCladdingVolume, above. Parameters ---------- dissolvedVolume: float - ''' + """ - assert dissolvedVolume >= 0.0, 'dissolved volume= %r; failed.' % ( - dissolvedVolume) + assert dissolvedVolume >= 0.0, "dissolved volume= %r; failed." % ( + dissolvedVolume + ) if dissolvedVolume == 0.0: return @@ -908,10 +953,10 @@ def __ReduceFuelVolume(self, dissolvedVolume): pi = math.pi # get this first - massDens = _GetAttribute(self, 'fuelMassDens') + massDens = _GetAttribute(self, "fuelMassDens") -# ................................................................................. -# reduce the volume of the fuel hollow sphere + # ................................................................................. + # reduce the volume of the fuel hollow sphere ro = self._fuelHollowSphereRo ri = self._fuelHollowSphereRi @@ -919,31 +964,30 @@ def __ReduceFuelVolume(self, dissolvedVolume): volume = 4.0 / 3.0 * pi * (ro**3 - ri**3) if dV < volume: - - ro = (ri**3 + 3.0 / 4.0 / pi * (volume - dV))**(1 / 3) + ro = (ri**3 + 3.0 / 4.0 / pi * (volume - dV)) ** (1 / 3) self._fuelHollowSphereRo = ro else: - self._fuelHollowSphereRo = 0.0 self._fuelHollowSphereRi = 0.0 - self._specs['Inner slug ID [cm]'] = 0.0 - self._specs['Inner slug OD [cm]'] = 0.0 - self._specs['Outer slug ID [cm]'] = 0.0 - self._specs['Outer slug OD [cm]'] = 0.0 + self._specs["Inner slug ID [cm]"] = 0.0 + self._specs["Inner slug OD [cm]"] = 0.0 + self._specs["Outer slug ID [cm]"] = 0.0 + self._specs["Outer slug OD [cm]"] = 0.0 + + self._specs["Slug length [cm]"] = 0.0 - self._specs['Slug length [cm]'] = 0.0 + self._specs["Cladding wall thickness [cm]"] = 0.0 + self._specs["Cladding end cap thickness [cm]"] = 0.0 - self._specs['Cladding wall thickness [cm]'] = 0.0 - self._specs['Cladding end cap thickness [cm]'] = 0.0 + # ................................................................................. + # Update the history of the fuel phase -# ................................................................................. -# Update the history of the fuel phase + volume = _GetAttribute(self, "equivalentFuelVolume") + self._fuelPhase.SetValue("volume", volume) - volume = _GetAttribute(self, 'equivalentFuelVolume') - self._fuelPhase.SetValue('volume', volume) + self._fuelPhase.SetValue("mass", massDens * volume) - self._fuelPhase.SetValue('mass', massDens * volume) -#============================ end class FuelSlug ================================= +# ============================ end class FuelSlug ================================= diff --git a/cortix/support/nuclear/nuclides.py b/cortix/support/nuclear/nuclides.py index ae587891..8124ff5e 100644 --- a/cortix/support/nuclear/nuclides.py +++ b/cortix/support/nuclear/nuclides.py @@ -8,7 +8,7 @@ # # Licensed under the University of Massachusetts Lowell LICENSE: # https://github.com/dpploy/cortix/blob/master/LICENSE.txt -''' +""" Author: Valmor de Almeida dealmeidav@ornl.gov; vfda Nuclides container. @@ -19,8 +19,9 @@ VFdALib support classes Sat Jun 27 14:46:49 EDT 2015 -''' -#********************************************************************************* +""" + +# ********************************************************************************* import os import sys import io @@ -32,296 +33,350 @@ from cortix.support.periodictable import ELEMENTS from cortix.support.periodictable import SERIES -#********************************************************************************* +# ********************************************************************************* -class Nuclides: -#********************************************************************************* -# Construction -#********************************************************************************* - - def __init__(self, - propertyDensities=pandas.DataFrame() - ): - - assert isinstance( - propertyDensities, type( - pandas.DataFrame())), 'fatal.' - - self.attributeNames = \ - ['nuclides', - 'isotopes', - 'massDens', - 'massCC', - 'radioactivityDens', - 'thermalDens', - 'heatDens', - 'gammaDens'] - - self.chemicalElementSeries = \ - ['alkali metals', 'alkali earth metals', 'lanthanides', 'actinides', - 'transition metals', 'noble gases', 'metalloids', 'fission products', 'nonmetals', - 'oxide fission products', 'halogens', 'minor actinides', 'volatile fission products', 'poor metals'] +class Nuclides: + # ********************************************************************************* + # Construction + # ********************************************************************************* + + def __init__(self, propertyDensities=pandas.DataFrame()): + assert isinstance(propertyDensities, type(pandas.DataFrame())), "fatal." + + self.attributeNames = [ + "nuclides", + "isotopes", + "massDens", + "massCC", + "radioactivityDens", + "thermalDens", + "heatDens", + "gammaDens", + ] + + self.chemicalElementSeries = [ + "alkali metals", + "alkali earth metals", + "lanthanides", + "actinides", + "transition metals", + "noble gases", + "metalloids", + "fission products", + "nonmetals", + "oxide fission products", + "halogens", + "minor actinides", + "volatile fission products", + "poor metals", + ] self.propertyDensities = propertyDensities return -#********************************************************************************* -# Public member functions -#********************************************************************************* + # ********************************************************************************* + # Public member functions + # ********************************************************************************* def GetAttribute(self, name, symbol=None, series=None): return self.__GetAttribute(name, symbol, series) -# def __str__( self ): -# s = ' %5s %5s %5s '+' molar mass: %6s '+' molar cc: %6s '+' mass cc: %6s '+' flag: %s '+'# atoms: %s'+' atoms: %s\n' -# return s % (self.name, self.formulaName, self.phase, self.molarMass, self.molarCC, self.massCC, self.flag, self.nAtoms, self.atoms) -# -# def __repr__( self ): -# s = ' %5s %5s %5s '+' molar mass: %6s '+' molar cc: %6s '+' mass cc: %6s '+' flag: %s '+'# atoms: %s'+' atoms: %s\n' -# return s % (self.name, self.formulaName, self.phase, self.molarMass, self.molarCC, self.massCC, self.flag, self.nAtoms, self.atoms) + # def __str__( self ): + # s = ' %5s %5s %5s '+' molar mass: %6s '+' molar cc: %6s '+' mass cc: %6s '+' flag: %s '+'# atoms: %s'+' atoms: %s\n' + # return s % (self.name, self.formulaName, self.phase, self.molarMass, self.molarCC, self.massCC, self.flag, self.nAtoms, self.atoms) + # + # def __repr__( self ): + # s = ' %5s %5s %5s '+' molar mass: %6s '+' molar cc: %6s '+' mass cc: %6s '+' flag: %s '+'# atoms: %s'+' atoms: %s\n' + # return s % (self.name, self.formulaName, self.phase, self.molarMass, self.molarCC, self.massCC, self.flag, self.nAtoms, self.atoms) -#********************************************************************************* -# Private helper functions (internal use: __) -#********************************************************************************* + # ********************************************************************************* + # Private helper functions (internal use: __) + # ********************************************************************************* -# Get property either overall or on a nuclide basis + # Get property either overall or on a nuclide basis def __GetAttribute(self, attributeName, symbol=None, series=None): - - assert attributeName in self.attributeNames, ' attribute name: %r; options: %r; fail.' % ( - attributeName, self.attributeNames) + assert attributeName in self.attributeNames, ( + " attribute name: %r; options: %r; fail." + % (attributeName, self.attributeNames) + ) if symbol is not None: - assert series is None, 'fail.' + assert series is None, "fail." if series is not None: - assert symbol is None, 'fail.' - - if attributeName == 'isotopes': - assert symbol is not None, 'need an element symbol.' + assert symbol is None, "fail." -# ................................................................................. -# isotopes python list + if attributeName == "isotopes": + assert symbol is not None, "need an element symbol." - if attributeName == 'isotopes': + # ................................................................................. + # isotopes python list + if attributeName == "isotopes": nuclidesNames = self.propertyDensities.index - isotopes = [x for x in nuclidesNames if x.split( - '-')[0].strip() == symbol] + isotopes = [x for x in nuclidesNames if x.split("-")[0].strip() == symbol] return isotopes -# ................................................................................. -# nuclides python list - - if attributeName == 'nuclides': + # ................................................................................. + # nuclides python list + if attributeName == "nuclides": if series is not None: # CREATE A HELPER FUNCTION FOR THIS; NOTE THIS IS USED BELOW # TOO!!! nuclidesNames = self.propertyDensities.index seriesNameMap = { - 'alkali metals': 'Alkali metals', - 'alkali earth metals': 'Alkaline earth metals', - 'lanthanides': 'Lanthanides', - 'actinides': 'Actinides', - 'transition metals': 'Transition metals', - 'noble gases': 'Noble gases', - 'metalloids': 'Metalloids', - 'fission products': 'fission products', - 'nonmetals': 'Nonmetals', - 'oxide fission products': 'oxide fission products', - 'halogens': 'Halogens', - 'minor actinides': 'minor actnides', - 'volatile fission products': 'volatile fission products', - 'poor metals': 'Poor metals'} + "alkali metals": "Alkali metals", + "alkali earth metals": "Alkaline earth metals", + "lanthanides": "Lanthanides", + "actinides": "Actinides", + "transition metals": "Transition metals", + "noble gases": "Noble gases", + "metalloids": "Metalloids", + "fission products": "fission products", + "nonmetals": "Nonmetals", + "oxide fission products": "oxide fission products", + "halogens": "Halogens", + "minor actinides": "minor actnides", + "volatile fission products": "volatile fission products", + "poor metals": "Poor metals", + } # - if series == 'fission products': - nuclides = [x for x in nuclidesNames if SERIES[ELEMENTS[x.split( - '-')[0].strip()].series] != seriesNameMap['actinides']] + if series == "fission products": + nuclides = [ + x + for x in nuclidesNames + if SERIES[ELEMENTS[x.split("-")[0].strip()].series] + != seriesNameMap["actinides"] + ] # - elif series == 'oxide fission products': - collec = [seriesNameMap['actinides'], - seriesNameMap['halogens'], - seriesNameMap['noble gases'] - ] - nuclides = [x for x in nuclidesNames if SERIES[ELEMENTS[x.split( - '-')[0].strip()].series] not in collec] - collec = ['C', 'N', 'O', 'H'] - nuclides = [x for x in nuclides if x.split( - '-')[0].strip() not in collec] + elif series == "oxide fission products": + collec = [ + seriesNameMap["actinides"], + seriesNameMap["halogens"], + seriesNameMap["noble gases"], + ] + nuclides = [ + x + for x in nuclidesNames + if SERIES[ELEMENTS[x.split("-")[0].strip()].series] + not in collec + ] + collec = ["C", "N", "O", "H"] + nuclides = [ + x for x in nuclides if x.split("-")[0].strip() not in collec + ] # - elif series == 'volatile fission products': - collec = [seriesNameMap['actinides'], - seriesNameMap['alkali metals'], - seriesNameMap['alkali earth metals'], - seriesNameMap['lanthanides'], - seriesNameMap['metalloids'], - seriesNameMap['transition metals'], - seriesNameMap['poor metals'] - ] - nuclides = [x for x in nuclidesNames if SERIES[ELEMENTS[x.split( - '-')[0].strip()].series] not in collec] - collec = ['C', 'O'] - nuclides = [x for x in nuclides if x.split( - '-')[0].strip() not in collec] + elif series == "volatile fission products": + collec = [ + seriesNameMap["actinides"], + seriesNameMap["alkali metals"], + seriesNameMap["alkali earth metals"], + seriesNameMap["lanthanides"], + seriesNameMap["metalloids"], + seriesNameMap["transition metals"], + seriesNameMap["poor metals"], + ] + nuclides = [ + x + for x in nuclidesNames + if SERIES[ELEMENTS[x.split("-")[0].strip()].series] + not in collec + ] + collec = ["C", "O"] + nuclides = [ + x for x in nuclides if x.split("-")[0].strip() not in collec + ] # - elif series == 'minor actinides': - nuclides = [x for x in nuclidesNames if SERIES[ELEMENTS[x.split( - '-')[0].strip()].series] == seriesNameMap['actinides']] - collec = ['U', 'Pu'] - nuclides = [x for x in nuclides if x.split( - '-')[0].strip() not in collec] + elif series == "minor actinides": + nuclides = [ + x + for x in nuclidesNames + if SERIES[ELEMENTS[x.split("-")[0].strip()].series] + == seriesNameMap["actinides"] + ] + collec = ["U", "Pu"] + nuclides = [ + x for x in nuclides if x.split("-")[0].strip() not in collec + ] # else: - nuclides = [x for x in nuclidesNames if SERIES[ELEMENTS[x.split( - '-')[0].strip()].series] == seriesNameMap[series]] + nuclides = [ + x + for x in nuclidesNames + if SERIES[ELEMENTS[x.split("-")[0].strip()].series] + == seriesNameMap[series] + ] return nuclides if series is None: - nuclidesNames = self.propertyDensities.index return list(nuclidesNames) -# ................................................................................. -# mass or mass concentration + # ................................................................................. + # mass or mass concentration - if attributeName == 'massCC': - colName = 'Mass CC [g/cc]' + if attributeName == "massCC": + colName = "Mass CC [g/cc]" -# ................................................................................. -# radioactivity + # ................................................................................. + # radioactivity - if attributeName == 'radioactivityDens': - colName = 'Radioactivity Dens. [Ci/cc]' + if attributeName == "radioactivityDens": + colName = "Radioactivity Dens. [Ci/cc]" -# ................................................................................. -# thermal + # ................................................................................. + # thermal - if attributeName == 'thermalDens' or attributeName == 'heatDens': - colName = 'Thermal Dens. [W/cc]' + if attributeName == "thermalDens" or attributeName == "heatDens": + colName = "Thermal Dens. [W/cc]" -# ................................................................................. -# gamma + # ................................................................................. + # gamma - if attributeName == 'gammaDens': - colName = 'Gamma Dens. [W/cc]' + if attributeName == "gammaDens": + colName = "Gamma Dens. [W/cc]" -# ................................................................................. -########################################################################## -# ................................................................................. + # ................................................................................. + ########################################################################## + # ................................................................................. -# ................................................................................. -# all nuclide content added + # ................................................................................. + # all nuclide content added if symbol is None and series is None: - density = 0.0 density = self.propertyDensities[colName].sum() return float(density) # avoid numpy.float64 type -# ................................................................................. -# get chemical element series + # ................................................................................. + # get chemical element series if series is not None: - density = 0.0 - assert series in self.chemicalElementSeries, 'series: %r; fail.' % ( - series) + assert series in self.chemicalElementSeries, "series: %r; fail." % (series) seriesNameMap = { - 'alkali metals': 'Alkali metals', - 'alkali earth metals': 'Alkaline earth metals', - 'lanthanides': 'Lanthanides', - 'actinides': 'Actinides', - 'transition metals': 'Transition metals', - 'noble gases': 'Noble gases', - 'metalloids': 'Metalloids', - 'fission products': 'fission products', - 'nonmetals': 'Nonmetals', - 'oxide fission products': 'oxide fission products', - 'halogens': 'Halogens', - 'minor actinides': 'minor actnides', - 'volatile fission products': 'volatile fission products', - 'poor metals': 'Poor metals'} + "alkali metals": "Alkali metals", + "alkali earth metals": "Alkaline earth metals", + "lanthanides": "Lanthanides", + "actinides": "Actinides", + "transition metals": "Transition metals", + "noble gases": "Noble gases", + "metalloids": "Metalloids", + "fission products": "fission products", + "nonmetals": "Nonmetals", + "oxide fission products": "oxide fission products", + "halogens": "Halogens", + "minor actinides": "minor actnides", + "volatile fission products": "volatile fission products", + "poor metals": "Poor metals", + } if series in self.chemicalElementSeries: - nuclidesNames = self.propertyDensities.index # - if series == 'fission products': - nuclides = [x for x in nuclidesNames if SERIES[ELEMENTS[x.split( - '-')[0].strip()].series] != seriesNameMap['actinides']] + if series == "fission products": + nuclides = [ + x + for x in nuclidesNames + if SERIES[ELEMENTS[x.split("-")[0].strip()].series] + != seriesNameMap["actinides"] + ] # - elif series == 'oxide fission products': - collec = [seriesNameMap['actinides'], - seriesNameMap['halogens'], - seriesNameMap['noble gases'] - ] - nuclides = [x for x in nuclidesNames if SERIES[ELEMENTS[x.split( - '-')[0].strip()].series] not in collec] - collec = ['C', 'N', 'O', 'H'] - nuclides = [x for x in nuclides if x.split( - '-')[0].strip() not in collec] + elif series == "oxide fission products": + collec = [ + seriesNameMap["actinides"], + seriesNameMap["halogens"], + seriesNameMap["noble gases"], + ] + nuclides = [ + x + for x in nuclidesNames + if SERIES[ELEMENTS[x.split("-")[0].strip()].series] + not in collec + ] + collec = ["C", "N", "O", "H"] + nuclides = [ + x for x in nuclides if x.split("-")[0].strip() not in collec + ] # - elif series == 'volatile fission products': - collec = [seriesNameMap['actinides'], - seriesNameMap['alkali metals'], - seriesNameMap['alkali earth metals'], - seriesNameMap['lanthanides'], - seriesNameMap['metalloids'], - seriesNameMap['transition metals'], - seriesNameMap['poor metals'] - ] - nuclides = [x for x in nuclidesNames if SERIES[ELEMENTS[x.split( - '-')[0].strip()].series] not in collec] - collec = ['C', 'O'] - nuclides = [x for x in nuclides if x.split( - '-')[0].strip() not in collec] + elif series == "volatile fission products": + collec = [ + seriesNameMap["actinides"], + seriesNameMap["alkali metals"], + seriesNameMap["alkali earth metals"], + seriesNameMap["lanthanides"], + seriesNameMap["metalloids"], + seriesNameMap["transition metals"], + seriesNameMap["poor metals"], + ] + nuclides = [ + x + for x in nuclidesNames + if SERIES[ELEMENTS[x.split("-")[0].strip()].series] + not in collec + ] + collec = ["C", "O"] + nuclides = [ + x for x in nuclides if x.split("-")[0].strip() not in collec + ] # - elif series == 'minor actinides': - nuclides = [x for x in nuclidesNames if SERIES[ELEMENTS[x.split( - '-')[0].strip()].series] == seriesNameMap['actinides']] - collec = ['U', 'Pu'] - nuclides = [x for x in nuclides if x.split( - '-')[0].strip() not in collec] + elif series == "minor actinides": + nuclides = [ + x + for x in nuclidesNames + if SERIES[ELEMENTS[x.split("-")[0].strip()].series] + == seriesNameMap["actinides"] + ] + collec = ["U", "Pu"] + nuclides = [ + x for x in nuclides if x.split("-")[0].strip() not in collec + ] # else: - nuclides = [x for x in nuclidesNames if SERIES[ELEMENTS[x.split( - '-')[0].strip()].series] == seriesNameMap[series]] + nuclides = [ + x + for x in nuclidesNames + if SERIES[ELEMENTS[x.split("-")[0].strip()].series] + == seriesNameMap[series] + ] -# print('fission products ',nuclides) + # print('fission products ',nuclides) for nuclide in nuclides: density += self.propertyDensities.loc[nuclide, colName] return float(density) # avoid numpy.float64 type -# ................................................................................. -# get specific nuclide (either the isotopes of the nuclide or the specific -# isotope) property + # ................................................................................. + # get specific nuclide (either the isotopes of the nuclide or the specific + # isotope) property if symbol is not None: - density = 0.0 - # single isotope - if len(symbol.split('-')) == 2: + # single isotope + if len(symbol.split("-")) == 2: density = self.propertyDensities.loc[symbol, colName] - # many isotopes + # many isotopes else: nuclidesNames = self.propertyDensities.index -# print(self.propertyDensities) + # print(self.propertyDensities) isotopes = [ - x for x in nuclidesNames if x.split('-')[0].strip() == symbol] -# print(isotopes) + x for x in nuclidesNames if x.split("-")[0].strip() == symbol + ] + # print(isotopes) for isotope in isotopes: density += self.propertyDensities.loc[isotope, colName] return float(density) # avoid numpy.float64.type -#======================= end class Nuclide ======================================= + +# ======================= end class Nuclide ======================================= diff --git a/cortix/support/nuclear/target_rod.py b/cortix/support/nuclear/target_rod.py index 4abed2bd..446b491a 100644 --- a/cortix/support/nuclear/target_rod.py +++ b/cortix/support/nuclear/target_rod.py @@ -2,20 +2,22 @@ # -*- coding: utf-8 -*- # This file is part of the Cortix toolkit environment # https://cortix.org -''' +""" Model of a target rod (assembly). -''' +""" + from copy import deepcopy from cortix.support.phase_new import PhaseNew as Phase + class TargetRod: - '''Target rod for irradiation and dissolution. + """Target rod for irradiation and dissolution. Two solid phases: cladding and compact - ''' + """ def __init__(self, specs=None, cladding_phase=None, compact_phase=None): - '''Constructor. + """Constructor. Parameters ---------- @@ -30,7 +32,7 @@ def __init__(self, specs=None, cladding_phase=None, compact_phase=None): Attributes ---------- - ''' + """ if specs is not None: assert isinstance(specs, dict) @@ -47,7 +49,8 @@ def __init__(self, specs=None, cladding_phase=None, compact_phase=None): else: self.compact_phase = deepcopy(compact_phase) -if __name__ == '__main__': + +if __name__ == "__main__": # Create an empty target rod trod = TargetRod() print(trod) diff --git a/cortix/support/periodictable.py b/cortix/support/periodictable.py index 1ba42c0c..6fa58aa1 100644 --- a/cortix/support/periodictable.py +++ b/cortix/support/periodictable.py @@ -83,14 +83,15 @@ from __future__ import division, print_function -__version__ = '2015.01.29' -__docformat__ = 'restructuredtext en' -__all__ = 'ELEMENTS', +__version__ = "2015.01.29" +__docformat__ = "restructuredtext en" +__all__ = ("ELEMENTS",) class lazyattr(object): """Lazy object attribute whose value is computed on first access.""" - __slots__ = ['func'] + + __slots__ = ["func"] def __init__(self, func): self.func = func @@ -191,31 +192,31 @@ def __repr__(self): isotopes = [] for massnum in sorted(self.isotopes): iso = self.isotopes[massnum] - isotopes.append("%i: Isotope(%s, %s, %i)" % ( - massnum, iso.mass, iso.abundance, massnum)) + isotopes.append( + "%i: Isotope(%s, %s, %i)" % (massnum, iso.mass, iso.abundance, massnum) + ) isotopes = ",\n ".join(isotopes) - description = word_wrap(self.description, linelen=66, indent=0, - joinstr=""" "\n \"""") + description = word_wrap( + self.description, linelen=66, indent=0, joinstr=""" "\n \"""" + ) description = """ e['%s'].description = (\n "%s\")""" % ( - self.symbol, description) + self.symbol, + description, + ) # return description result = [ - "Element(\n %i, '%s', '%s'" % ( - self.number, self.symbol, self.name), - "group=%s, period=%s, block='%s', series=%i" % ( - self.group, self.period, self.block, self.series), - "mass=%s, eleneg=%s, eleaffin=%s" % ( - self.mass, self.eleneg, self.eleaffin), - "covrad=%s, atmrad=%s, vdwrad=%s" % ( - self.covrad, self.atmrad, self.vdwrad), - "tboil=%s, tmelt=%s, density=%s" % ( - self.tboil, self.tmelt, self.density), + "Element(\n %i, '%s', '%s'" % (self.number, self.symbol, self.name), + "group=%s, period=%s, block='%s', series=%i" + % (self.group, self.period, self.block, self.series), + "mass=%s, eleneg=%s, eleaffin=%s" % (self.mass, self.eleneg, self.eleaffin), + "covrad=%s, atmrad=%s, vdwrad=%s" % (self.covrad, self.atmrad, self.vdwrad), + "tboil=%s, tmelt=%s, density=%s" % (self.tboil, self.tmelt, self.density), "eleconfig='%s'" % self.eleconfig, "oxistates='%s'" % self.oxistates, "ionenergy=(%s)" % ionenergy, - "isotopes={%s})" % isotopes + "isotopes={%s})" % isotopes, ] return ",\n ".join(result) @@ -244,10 +245,10 @@ def exactmass(self): def eleconfig_dict(self): """Return electron configuration as dict.""" adict = {} - if self.eleconfig.startswith('['): - base = self.eleconfig.split(' ', 1)[0][1:-1] + if self.eleconfig.startswith("["): + base = self.eleconfig.split(" ", 1)[0][1:-1] adict.update(ELEMENTS[base].eleconfig_dict) - for e in self.eleconfig.split()[bool(adict):]: + for e in self.eleconfig.split()[bool(adict) :]: adict[(int(e[0]), e[1])] = int(e[2:]) if len(e) > 2 else 1 return adict @@ -273,10 +274,12 @@ def validate(self): if self.number != self.protons: raise ValueError( - "%s - atomic number must equal proton number" % self.symbol) + "%s - atomic number must equal proton number" % self.symbol + ) if self.protons != sum(self.eleshells): raise ValueError( - "%s - number of protons must equal electrons" % self.symbol) + "%s - number of protons must equal electrons" % self.symbol + ) mass = 0.0 frac = 0.0 @@ -285,24 +288,27 @@ def validate(self): frac += iso.abundance if abs(mass - self.mass) > 0.03: raise ValueError( - "%s - average of isotope masses (%.4f) != mass (%.4f)" % ( - self.symbol, mass, self.mass)) + "%s - average of isotope masses (%.4f) != mass (%.4f)" + % (self.symbol, mass, self.mass) + ) if abs(frac - 1.0) > 1e-9: - raise ValueError( - "%s - sum of isotope abundances != 1.0" % self.symbol) + raise ValueError("%s - sum of isotope abundances != 1.0" % self.symbol) import numpy as np + infinity = np.inf # replace infinity with math.inf for python 3.5 or greater class Isotope(object): """Isotope relative atomic mass, abundance, massnumber.""" - __slots__ = ['mass', 'abundance', 'massnumber', 'halfLife', 'halfLifeUnit'] - def __init__(self, mass=0.0, abundance=1.0, massnumber=0, - halfLife=infinity, halfLifeUnit='s'): + __slots__ = ["mass", "abundance", "massnumber", "halfLife", "halfLifeUnit"] + + def __init__( + self, mass=0.0, abundance=1.0, massnumber=0, halfLife=infinity, halfLifeUnit="s" + ): self.mass = mass self.abundance = abundance self.massnumber = massnumber @@ -310,12 +316,22 @@ def __init__(self, mass=0.0, abundance=1.0, massnumber=0, self.halfLifeUnit = halfLifeUnit def __str__(self): - return "%i, %.4f, %.6f%%, %.4f, %s" % (self.massnumber, self.mass, - self.abundance * 100, self.halfLife, self.halfLifeUnit) + return "%i, %.4f, %.6f%%, %.4f, %s" % ( + self.massnumber, + self.mass, + self.abundance * 100, + self.halfLife, + self.halfLifeUnit, + ) def __repr__(self): return "Isotope(%s, %s, %s, %s, %s)" % ( - repr(self.mass), repr(self.abundance), repr(self.massnumber), repr(self.halfLife), repr(self.halfLifeUnit)) + repr(self.mass), + repr(self.abundance), + repr(self.massnumber), + repr(self.halfLife), + repr(self.halfLifeUnit), + ) class ElementsDict(object): @@ -360,1551 +376,3629 @@ def __getitem__(self, key): ELEMENTS = ElementsDict( Element( - 1, 'H', 'Hydrogen', - group=1, period=1, block='s', series=1, - mass=1.00794, eleneg=2.2, eleaffin=0.75420375, - covrad=0.32, atmrad=0.79, vdwrad=1.2, - tboil=20.28, tmelt=13.81, density=0.084, - eleconfig='1s', - oxistates='1*, -1', - ionenergy=(13.5984, ), - isotopes={1: Isotope(1.0078250321, 0.999885, 1), - 2: Isotope(2.014101778, 0.000115, 2), - 3: Isotope(3.0160492777, 0.000, 3, 12.32, 'y')}), # y -> year - Element( - 2, 'He', 'Helium', - group=18, period=1, block='s', series=2, - mass=4.002602, eleneg=0.0, eleaffin=0.0, - covrad=0.93, atmrad=0.49, vdwrad=1.4, - tboil=4.216, tmelt=0.95, density=0.1785, - eleconfig='1s2', - oxistates='*', - ionenergy=(24.5874, 54.416, ), - isotopes={3: Isotope(3.0160293097, 1.37e-06, 3), - 4: Isotope(4.0026032497, 0.99999863, 4)}), - Element( - 3, 'Li', 'Lithium', - group=1, period=2, block='s', series=3, - mass=6.941, eleneg=0.98, eleaffin=0.618049, - covrad=1.23, atmrad=2.05, vdwrad=1.82, - tboil=1615.0, tmelt=453.7, density=0.53, - eleconfig='[He] 2s', - oxistates='1*', - ionenergy=(5.3917, 75.638, 122.451, ), - isotopes={6: Isotope(6.0151223, 0.0759, 6), - 7: Isotope(7.016004, 0.9241, 7)}), - Element( - 4, 'Be', 'Beryllium', - group=2, period=2, block='s', series=4, - mass=9.012182, eleneg=1.57, eleaffin=0.0, - covrad=0.9, atmrad=1.4, vdwrad=0.0, - tboil=3243.0, tmelt=1560.0, density=1.85, - eleconfig='[He] 2s2', - oxistates='2*', - ionenergy=(9.3227, 18.211, 153.893, 217.713, ), - isotopes={9: Isotope(9.0121821, 1.0, 9), - 10: Isotope(10.01353382, 0.0, 10, 1.51, 'My')}), # My -> Mega year - Element( - 5, 'B', 'Boron', - group=13, period=2, block='p', series=5, - mass=10.811, eleneg=2.04, eleaffin=0.279723, - covrad=0.82, atmrad=1.17, vdwrad=0.0, - tboil=4275.0, tmelt=2365.0, density=2.46, - eleconfig='[He] 2s2 2p', - oxistates='3*', - ionenergy=(8.298, 25.154, 37.93, 59.368, 340.217, ), - isotopes={10: Isotope(10.012937, 0.199, 10), - 11: Isotope(11.0093055, 0.801, 11)}), - Element( - 6, 'C', 'Carbon', - group=14, period=2, block='p', series=1, - mass=12.0107, eleneg=2.55, eleaffin=1.262118, - covrad=0.77, atmrad=0.91, vdwrad=1.7, - tboil=5100.0, tmelt=3825.0, density=3.51, - eleconfig='[He] 2s2 2p2', - oxistates='4*, 2, -4*', - ionenergy=(11.2603, 24.383, 47.877, 64.492, 392.077, - 489.981, ), - isotopes={12: Isotope(12.0, 0.9893, 12), - 13: Isotope(13.0033548378, 0.0107, 13), - 14: Isotope(14.0032419887, 0.00, 14)}), - Element( - 7, 'N', 'Nitrogen', - group=15, period=2, block='p', series=1, - mass=14.0067, eleneg=3.04, eleaffin=-0.07, - covrad=0.75, atmrad=0.75, vdwrad=1.55, - tboil=77.344, tmelt=63.15, density=1.17, - eleconfig='[He] 2s2 2p3', - oxistates='5, 4, 3, 2, -3*', - ionenergy=(14.5341, 39.601, 47.488, 77.472, 97.888, - 522.057, 667.029, ), - isotopes={14: Isotope(14.0030740052, 0.99632, 14), - 15: Isotope(15.0001088984, 0.00368, 15)}), - Element( - 8, 'O', 'Oxygen', - group=16, period=2, block='p', series=1, - mass=15.9994, eleneg=3.44, eleaffin=1.461112, - covrad=0.73, atmrad=0.65, vdwrad=1.52, - tboil=90.188, tmelt=54.8, density=1.33, - eleconfig='[He] 2s2 2p4', - oxistates='-2*, -1', - ionenergy=(13.6181, 35.116, 54.934, 54.934, 77.412, - 113.896, 138.116, 739.315, 871.387, ), - isotopes={16: Isotope(15.9949146221, 0.99757, 16), - 17: Isotope(16.9991315, 0.00038, 17), - 18: Isotope(17.9991604, 0.00205, 18)}), - Element( - 9, 'F', 'Fluorine', - group=17, period=2, block='p', series=6, - mass=18.9984032, eleneg=3.98, eleaffin=3.4011887, - covrad=0.72, atmrad=0.57, vdwrad=1.47, - tboil=85.0, tmelt=53.55, density=1.58, - eleconfig='[He] 2s2 2p5', - oxistates='-1*', - ionenergy=(17.4228, 34.97, 62.707, 87.138, 114.24, - 157.161, 185.182, 953.886, 1103.089, ), - isotopes={19: Isotope(18.9984032, 1.0, 19)}), - Element( - 10, 'Ne', 'Neon', - group=18, period=2, block='p', series=2, - mass=20.1797, eleneg=0.0, eleaffin=0.0, - covrad=0.71, atmrad=0.51, vdwrad=1.54, - tboil=27.1, tmelt=24.55, density=0.8999, - eleconfig='[He] 2s2 2p6', - oxistates='*', - ionenergy=(21.5645, 40.962, 63.45, 97.11, 126.21, - 157.93, 207.27, 239.09, 1195.797, 1362.164, ), - isotopes={20: Isotope(19.9924401759, 0.9048, 20), - 21: Isotope(20.99384674, 0.0027, 21), - 22: Isotope(21.99138551, 0.0925, 22)}), - Element( - 11, 'Na', 'Sodium', - group=1, period=3, block='s', series=3, - mass=22.98977, eleneg=0.93, eleaffin=0.547926, - covrad=1.54, atmrad=2.23, vdwrad=2.27, - tboil=1156.0, tmelt=371.0, density=0.97, - eleconfig='[Ne] 3s', - oxistates='1*', - ionenergy=(5.1391, 47.286, 71.64, 98.91, 138.39, - 172.15, 208.47, 264.18, 299.87, 1465.091, - 1648.659, ), - isotopes={23: Isotope(22.98976967, 1.0, 23)}), - Element( - 12, 'Mg', 'Magnesium', - group=2, period=3, block='s', series=4, - mass=24.305, eleneg=1.31, eleaffin=0.0, - covrad=1.36, atmrad=1.72, vdwrad=1.73, - tboil=1380.0, tmelt=922.0, density=1.74, - eleconfig='[Ne] 3s2', - oxistates='2*', - ionenergy=(7.6462, 15.035, 80.143, 109.24, 141.26, - 186.5, 224.94, 265.9, 327.95, 367.53, - 1761.802, 1962.613, ), - isotopes={24: Isotope(23.9850419, 0.7899, 24), - 25: Isotope(24.98583702, 0.1, 25), - 26: Isotope(25.98259304, 0.1101, 26)}), - Element( - 13, 'Al', 'Aluminium', - group=13, period=3, block='p', series=7, - mass=26.981538, eleneg=1.61, eleaffin=0.43283, - covrad=1.18, atmrad=1.82, vdwrad=0.0, - tboil=2740.0, tmelt=933.5, density=2.7, - eleconfig='[Ne] 3s2 3p', - oxistates='3*', - ionenergy=(5.9858, 18.828, 28.447, 119.99, 153.71, - 190.47, 241.43, 284.59, 330.21, 398.57, - 442.07, 2085.983, 2304.08, ), - isotopes={27: Isotope(26.98153844, 1.0, 27)}), - Element( - 14, 'Si', 'Silicon', - group=14, period=3, block='p', series=5, - mass=28.0855, eleneg=1.9, eleaffin=1.389521, - covrad=1.11, atmrad=1.46, vdwrad=2.1, - tboil=2630.0, tmelt=1683.0, density=2.33, - eleconfig='[Ne] 3s2 3p2', - oxistates='4*, -4', - ionenergy=(8.1517, 16.345, 33.492, 45.141, 166.77, - 205.05, 246.52, 303.17, 351.1, 401.43, - 476.06, 523.5, 2437.676, 2673.108, ), - isotopes={28: Isotope(27.9769265327, 0.922297, 28), - 29: Isotope(28.97649472, 0.046832, 29), - 30: Isotope(29.97377022, 0.030871, 30)}), - Element( - 15, 'P', 'Phosphorus', - group=15, period=3, block='p', series=1, - mass=30.973761, eleneg=2.19, eleaffin=0.7465, - covrad=1.06, atmrad=1.23, vdwrad=1.8, - tboil=553.0, tmelt=317.3, density=1.82, - eleconfig='[Ne] 3s2 3p3', - oxistates='5*, 3, -3', - ionenergy=(10.4867, 19.725, 30.18, 51.37, 65.023, - 220.43, 263.22, 309.41, 371.73, 424.5, - 479.57, 560.41, 611.85, 2816.943, 3069.762, ), - isotopes={31: Isotope(30.97376151, 1.0, 31)}), - Element( - 16, 'S', 'Sulfur', - group=16, period=3, block='p', series=1, - mass=32.065, eleneg=2.58, eleaffin=2.0771029, - covrad=1.02, atmrad=1.09, vdwrad=1.8, - tboil=717.82, tmelt=392.2, density=2.06, - eleconfig='[Ne] 3s2 3p4', - oxistates='6*, 4, 2, -2', - ionenergy=(10.36, 23.33, 34.83, 47.3, 72.68, - 88.049, 280.93, 328.23, 379.1, 447.09, - 504.78, 564.65, 651.63, 707.14, 3223.836, - 3494.099, ), - isotopes={32: Isotope(31.97207069, 0.9493, 32), - 33: Isotope(32.9714585, 0.0076, 33), - 34: Isotope(33.96786683, 0.0429, 34), - 36: Isotope(35.96708088, 0.0002, 36)}), - Element( - 17, 'Cl', 'Chlorine', - group=17, period=3, block='p', series=6, - mass=35.453, eleneg=3.16, eleaffin=3.612724, - covrad=0.99, atmrad=0.97, vdwrad=1.75, - tboil=239.18, tmelt=172.17, density=2.95, - eleconfig='[Ne] 3s2 3p5', - oxistates='7, 5, 3, 1, -1*', - ionenergy=(12.9676, 23.81, 39.61, 53.46, 67.8, - 98.03, 114.193, 348.28, 400.05, 455.62, - 529.97, 591.97, 656.69, 749.75, 809.39, - 3658.425, 3946.193, ), - isotopes={35: Isotope(34.96885271, 0.7578, 35), - 37: Isotope(36.9659026, 0.2422, 37)}), - Element( - 18, 'Ar', 'Argon', - group=18, period=3, block='p', series=2, - mass=39.948, eleneg=0.0, eleaffin=0.0, - covrad=0.98, atmrad=0.88, vdwrad=1.88, - tboil=87.45, tmelt=83.95, density=1.66, - eleconfig='[Ne] 3s2 3p6', - oxistates='*', - ionenergy=(15.7596, 27.629, 40.74, 59.81, 75.02, - 91.007, 124.319, 143.456, 422.44, 478.68, - 538.95, 618.24, 686.09, 755.73, 854.75, - 918.0, 4120.778, 4426.114, ), - isotopes={36: Isotope(35.96754628, 0.003365, 36), - 38: Isotope(37.9627322, 0.000632, 38), - 40: Isotope(39.962383123, 0.996003, 40)}), - Element( - 19, 'K', 'Potassium', - group=1, period=4, block='s', series=3, - mass=39.0983, eleneg=0.82, eleaffin=0.501459, - covrad=2.03, atmrad=2.77, vdwrad=2.75, - tboil=1033.0, tmelt=336.8, density=0.86, - eleconfig='[Ar] 4s', - oxistates='1*', - ionenergy=(4.3407, 31.625, 45.72, 60.91, 82.66, - 100.0, 117.56, 154.86, 175.814, 503.44, - 564.13, 629.09, 714.02, 787.13, 861.77, - 968.0, 1034.0, 4610.955, 4933.931, ), - isotopes={39: Isotope(38.9637069, 0.932581, 39), - 40: Isotope(39.96399867, 0.000117, 40), - 41: Isotope(40.96182597, 0.067302, 41)}), - Element( - 20, 'Ca', 'Calcium', - group=2, period=4, block='s', series=4, - mass=40.078, eleneg=1.0, eleaffin=0.02455, - covrad=1.74, atmrad=2.23, vdwrad=0.0, - tboil=1757.0, tmelt=1112.0, density=1.54, - eleconfig='[Ar] 4s2', - oxistates='2*', - ionenergy=(6.1132, 11.71, 50.908, 67.1, 84.41, - 108.78, 127.7, 147.24, 188.54, 211.27, - 591.25, 656.39, 726.03, 816.61, 895.12, - 974.0, 1087.0, 1157.0, 5129.045, 5469.738, ), - isotopes={40: Isotope(39.9625912, 0.96941, 40), - 42: Isotope(41.9586183, 0.00647, 42), - 43: Isotope(42.9587668, 0.00135, 43), - 44: Isotope(43.9554811, 0.02086, 44), - 46: Isotope(45.9536928, 4e-05, 46), - 48: Isotope(47.952534, 0.00187, 48)}), - Element( - 21, 'Sc', 'Scandium', - group=3, period=4, block='d', series=8, - mass=44.95591, eleneg=1.36, eleaffin=0.188, - covrad=1.44, atmrad=2.09, vdwrad=0.0, - tboil=3109.0, tmelt=1814.0, density=2.99, - eleconfig='[Ar] 3d 4s2', - oxistates='3*', - ionenergy=(6.5615, 12.8, 24.76, 73.47, 91.66, - 11.1, 138.0, 158.7, 180.02, 225.32, - 225.32, 685.89, 755.47, 829.79, 926.0, ), - isotopes={45: Isotope(44.9559102, 1.0, 45)}), - Element( - 22, 'Ti', 'Titanium', - group=4, period=4, block='d', series=8, - mass=47.867, eleneg=1.54, eleaffin=0.084, - covrad=1.32, atmrad=2.0, vdwrad=0.0, - tboil=3560.0, tmelt=1935.0, density=4.51, - eleconfig='[Ar] 3d2 4s2', - oxistates='4*, 3', - ionenergy=(6.8281, 13.58, 27.491, 43.266, 99.22, - 119.36, 140.8, 168.5, 193.5, 193.2, - 215.91, 265.23, 291.497, 787.33, 861.33, ), - isotopes={46: Isotope(45.9526295, 0.0825, 46), - 47: Isotope(46.9517638, 0.0744, 47), - 48: Isotope(47.9479471, 0.7372, 48), - 49: Isotope(48.9478708, 0.0541, 49), - 50: Isotope(49.9447921, 0.0518, 50)}), - Element( - 23, 'V', 'Vanadium', - group=5, period=4, block='d', series=8, - mass=50.9415, eleneg=1.63, eleaffin=0.525, - covrad=1.22, atmrad=1.92, vdwrad=0.0, - tboil=3650.0, tmelt=2163.0, density=6.09, - eleconfig='[Ar] 3d3 4s2', - oxistates='5*, 4, 3, 2, 0', - ionenergy=(6.7462, 14.65, 29.31, 46.707, 65.23, - 128.12, 150.17, 173.7, 205.8, 230.5, - 255.04, 308.25, 336.267, 895.58, 974.02, ), - isotopes={50: Isotope(49.9471628, 0.0025, 50), - 51: Isotope(50.9439637, 0.9975, 51)}), - Element( - 24, 'Cr', 'Chromium', - group=6, period=4, block='d', series=8, - mass=51.9961, eleneg=1.66, eleaffin=0.67584, - covrad=1.18, atmrad=1.85, vdwrad=0.0, - tboil=2945.0, tmelt=2130.0, density=7.14, - eleconfig='[Ar] 3d5 4s', - oxistates='6, 3*, 2, 0', - ionenergy=(6.7665, 16.5, 30.96, 49.1, 69.3, - 90.56, 161.1, 184.7, 209.3, 244.4, - 270.8, 298.0, 355.0, 384.3, 1010.64, ), - isotopes={50: Isotope(49.9460496, 0.04345, 50), - 52: Isotope(51.9405119, 0.83789, 52), - 53: Isotope(52.9406538, 0.09501, 53), - 54: Isotope(53.9388849, 0.02365, 54)}), - Element( - 25, 'Mn', 'Manganese', - group=7, period=4, block='d', series=8, - mass=54.938049, eleneg=1.55, eleaffin=0.0, - covrad=1.17, atmrad=1.79, vdwrad=0.0, - tboil=2235.0, tmelt=1518.0, density=7.44, - eleconfig='[Ar] 3d5 4s2', - oxistates='7, 6, 4, 3, 2*, 0, -1', - ionenergy=(7.434, 15.64, 33.667, 51.2, 72.4, - 95.0, 119.27, 196.46, 221.8, 248.3, - 286.0, 314.4, 343.6, 404.0, 435.3, - 1136.2, ), - isotopes={55: Isotope(54.9380496, 1.0, 55)}), - Element( - 26, 'Fe', 'Iron', - group=8, period=4, block='d', series=8, - mass=55.845, eleneg=1.83, eleaffin=0.151, - covrad=1.17, atmrad=1.72, vdwrad=0.0, - tboil=3023.0, tmelt=1808.0, density=7.874, - eleconfig='[Ar] 3d6 4s2', - oxistates='6, 3*, 2, 0, -2', - ionenergy=(7.9024, 16.18, 30.651, 54.8, 75.0, - 99.0, 125.0, 151.06, 235.04, 262.1, - 290.4, 330.8, 361.0, 392.2, 457.0, - 485.5, 1266.1, ), - isotopes={54: Isotope(53.9396148, 0.05845, 54), - 56: Isotope(55.9349421, 0.91754, 56), - 57: Isotope(56.9353987, 0.02119, 57), - 58: Isotope(57.9332805, 0.00282, 58)}), - Element( - 27, 'Co', 'Cobalt', - group=9, period=4, block='d', series=8, - mass=58.9332, eleneg=1.88, eleaffin=0.6633, - covrad=1.16, atmrad=1.67, vdwrad=0.0, - tboil=3143.0, tmelt=1768.0, density=8.89, - eleconfig='[Ar] 3d7 4s2', - oxistates='3, 2*, 0, -1', - ionenergy=(7.881, 17.06, 33.5, 51.3, 79.5, - 102.0, 129.0, 157.0, 186.13, 276.0, - 305.0, 336.0, 376.0, 411.0, 444.0, - 512.0, 546.8, 1403.0, ), - isotopes={59: Isotope(58.9332002, 1.0, 59)}), - Element( - 28, 'Ni', 'Nickel', - group=10, period=4, block='d', series=8, - mass=58.6934, eleneg=1.91, eleaffin=1.15716, - covrad=1.15, atmrad=1.62, vdwrad=1.63, - tboil=3005.0, tmelt=1726.0, density=8.91, - eleconfig='[Ar] 3d8 4s2', - oxistates='3, 2*, 0', - ionenergy=(7.6398, 18.168, 35.17, 54.9, 75.5, - 108.0, 133.0, 162.0, 193.0, 224.5, - 321.2, 352.0, 384.0, 430.0, 464.0, - 499.0, 571.0, 607.2, 1547.0, ), - isotopes={58: Isotope(57.9353479, 0.680769, 58), - 60: Isotope(59.9307906, 0.262231, 60), - 61: Isotope(60.9310604, 0.011399, 61), - 62: Isotope(61.9283488, 0.036345, 62), - 64: Isotope(63.9279696, 0.009256, 64)}), - Element( - 29, 'Cu', 'Copper', - group=11, period=4, block='d', series=8, - mass=63.546, eleneg=1.9, eleaffin=1.23578, - covrad=1.17, atmrad=1.57, vdwrad=1.4, - tboil=2840.0, tmelt=1356.6, density=8.92, - eleconfig='[Ar] 3d10 4s', - oxistates='2*, 1', - ionenergy=(7.7264, 20.292, 26.83, 55.2, 79.9, - 103.0, 139.0, 166.0, 199.0, 232.0, - 266.0, 368.8, 401.0, 435.0, 484.0, - 520.0, 557.0, 633.0, 671.0, 1698.0, ), - isotopes={63: Isotope(62.9296011, 0.6917, 63), - 65: Isotope(64.9277937, 0.3083, 65)}), - Element( - 30, 'Zn', 'Zinc', - group=12, period=4, block='d', series=8, - mass=65.409, eleneg=1.65, eleaffin=0.0, - covrad=1.25, atmrad=1.53, vdwrad=1.39, - tboil=1180.0, tmelt=692.73, density=7.14, - eleconfig='[Ar] 3d10 4s2', - oxistates='2*', - ionenergy=(9.3942, 17.964, 39.722, 59.4, 82.6, - 108.0, 134.0, 174.0, 203.0, 238.0, - 274.0, 310.8, 419.7, 454.0, 490.0, - 542.0, 579.0, 619.0, 698.8, 738.0, - 1856.0, ), - isotopes={64: Isotope(63.9291466, 0.4863, 64), - 66: Isotope(65.9260368, 0.279, 66), - 67: Isotope(66.9271309, 0.041, 67), - 68: Isotope(67.9248476, 0.1875, 68), - 70: Isotope(69.925325, 0.0062, 70)}), - Element( - 31, 'Ga', 'Gallium', - group=13, period=4, block='p', series=7, - mass=69.723, eleneg=1.81, eleaffin=0.41, - covrad=1.26, atmrad=1.81, vdwrad=1.87, - tboil=2478.0, tmelt=302.92, density=5.91, - eleconfig='[Ar] 3d10 4s2 4p', - oxistates='3*', - ionenergy=(5.9993, 20.51, 30.71, 64.0, ), - isotopes={69: Isotope(68.925581, 0.60108, 69), - 71: Isotope(70.924705, 0.39892, 71)}), - Element( - 32, 'Ge', 'Germanium', - group=14, period=4, block='p', series=5, - mass=72.64, eleneg=2.01, eleaffin=1.232712, - covrad=1.22, atmrad=1.52, vdwrad=0.0, - tboil=3107.0, tmelt=1211.5, density=5.32, - eleconfig='[Ar] 3d10 4s2 4p2', - oxistates='4*', - ionenergy=(7.8994, 15.934, 34.22, 45.71, 93.5, ), - isotopes={70: Isotope(69.9242504, 0.2084, 70), - 72: Isotope(71.9220762, 0.2754, 72), - 73: Isotope(72.9234594, 0.0773, 73), - 74: Isotope(73.9211782, 0.3628, 74), - 76: Isotope(75.9214027, 0.0761, 76, 1.58, 'Zy')}), # Zetta year - Element( - 33, 'As', 'Arsenic', - group=15, period=4, block='p', series=5, - mass=74.9216, eleneg=2.18, eleaffin=0.814, - covrad=1.2, atmrad=1.33, vdwrad=1.85, - tboil=876.0, tmelt=1090.0, density=5.72, - eleconfig='[Ar] 3d10 4s2 4p3', - oxistates='5, 3*, -3', - ionenergy=(9.7886, 18.633, 28.351, 50.13, 62.63, - 127.6, ), - isotopes={75: Isotope(74.9215964, 1.0, 75)}), - Element( - 34, 'Se', 'Selenium', - group=16, period=4, block='p', series=5, - mass=78.96, eleneg=2.55, eleaffin=2.02067, - covrad=1.16, atmrad=1.22, vdwrad=1.9, - tboil=958.0, tmelt=494.0, density=4.82, - eleconfig='[Ar] 3d10 4s2 4p4', - oxistates='6, 4*, -2', - ionenergy=(9.7524, 21.9, 30.82, 42.944, 68.3, - 81.7, 155.4, ), - isotopes={74: Isotope(73.9224766, 0.0089, 74), - 75: Isotope(74.9225234, 0.0000, 75), - 76: Isotope(75.9192141, 0.0937, 76), - 77: Isotope(76.9199146, 0.0763, 77), - 78: Isotope(77.9173095, 0.2377, 78), - 79: Isotope(78.9184991, 0.0000, 79, 295, 'ky'), - 80: Isotope(79.9165218, 0.4961, 80), - 82: Isotope(81.9167, 0.0873, 82, 97, 'Ey')}), # Exa year - Element( - 35, 'Br', 'Bromine', - group=17, period=4, block='p', series=6, - mass=79.904, eleneg=2.96, eleaffin=3.363588, - covrad=1.14, atmrad=1.12, vdwrad=1.85, - tboil=331.85, tmelt=265.95, density=3.14, - eleconfig='[Ar] 3d10 4s2 4p5', - oxistates='7, 5, 3, 1, -1*', - ionenergy=(11.8138, 21.8, 36.0, 47.3, 59.7, - 88.6, 103.0, 192.8, ), - isotopes={79: Isotope(78.9183376, 0.5069, 79), - 81: Isotope(80.916291, 0.4931, 81)}), - Element( - 36, 'Kr', 'Krypton', - group=18, period=4, block='p', series=2, - mass=83.798, eleneg=0.0, eleaffin=0.0, - covrad=1.12, atmrad=1.03, vdwrad=2.02, - tboil=120.85, tmelt=116.0, density=4.48, - eleconfig='[Ar] 3d10 4s2 4p6', - oxistates='2*', - ionenergy=(13.9996, 24.359, 36.95, 52.5, 64.7, - 78.5, 110.0, 126.0, 230.39, ), - isotopes={78: Isotope(77.920386, 0.0035, 78), - 80: Isotope(79.916378, 0.0228, 80), - 81: Isotope(80.9165920, 0.0000, 81), - 82: Isotope(81.9134846, 0.1158, 82), - 83: Isotope(82.914136, 0.1149, 83), - 84: Isotope(83.911507, 0.57, 84), - 85: Isotope(84.9125273, 0.00, 85, 10.776, 'y'), - 86: Isotope(85.9106103, 0.173, 86)}), - Element( - 37, 'Rb', 'Rubidium', - group=1, period=5, block='s', series=3, - mass=85.4678, eleneg=0.82, eleaffin=0.485916, - covrad=2.16, atmrad=2.98, vdwrad=0.0, - tboil=961.0, tmelt=312.63, density=1.53, - eleconfig='[Kr] 5s', - oxistates='1*', - ionenergy=(4.1771, 27.28, 40.0, 52.6, 71.0, - 84.4, 99.2, 136.0, 150.0, 277.1, ), - isotopes={85: Isotope(84.9117893, 0.7217, 85), - 87: Isotope(86.9091835, 0.2783, 87, 42.23, 'Gy')}), - Element( - 38, 'Sr', 'Strontium', - group=2, period=5, block='s', series=4, - mass=87.62, eleneg=0.95, eleaffin=0.05206, - covrad=1.91, atmrad=2.45, vdwrad=0.0, - tboil=1655.0, tmelt=1042.0, density=2.63, - eleconfig='[Kr] 5s2', - oxistates='2*', - ionenergy=(5.6949, 11.03, 43.6, 57.0, 71.6, - 90.8, 106.0, 122.3, 162.0, 177.0, - 324.1, ), - isotopes={84: Isotope(83.913425, 0.0056, 84), - 86: Isotope(85.9092624, 0.0986, 86), - 87: Isotope(86.9088793, 0.07, 87), - 88: Isotope(87.9056143, 0.8258, 88), - 89: Isotope(88.9074507, 0.0000, 89, 50.53, 'd'), # day - 90: Isotope(89.9077379, 0.0000, 90, 28.79, 'y')}), - Element( - 39, 'Y', 'Yttrium', - group=3, period=5, block='d', series=8, - mass=88.90585, eleneg=1.22, eleaffin=0.307, - covrad=1.62, atmrad=2.27, vdwrad=0.0, - tboil=3611.0, tmelt=1795.0, density=4.47, - eleconfig='[Kr] 4d 5s2', - oxistates='3*', - ionenergy=(6.2173, 12.24, 20.52, 61.8, 77.0, - 93.0, 116.0, 129.0, 146.52, 191.0, - 206.0, 374.0, ), - isotopes={88: Isotope(87.9095011, 0.0, 88), - 89: Isotope(88.9058479, 1.0, 89), - 90: Isotope(89.9071519, 0.0, 90, 64.0, 'h'), # hour - 91: Isotope(90.9073048, 0.0, 91, 58.51, 'd')}), - Element( - 40, 'Zr', 'Zirconium', - group=4, period=5, block='d', series=8, - mass=91.224, eleneg=1.33, eleaffin=0.426, - covrad=1.45, atmrad=2.16, vdwrad=0.0, - tboil=4682.0, tmelt=2128.0, density=6.51, - eleconfig='[Kr] 4d2 5s2', - oxistates='4*', - ionenergy=(6.6339, 13.13, 22.99, 34.34, 81.5, ), - isotopes={90: Isotope(89.9047037, 0.5145, 90), - 91: Isotope(90.905645, 0.1122, 91), - 92: Isotope(91.9050401, 0.1715, 92), - 93: Isotope(92.9064760, 0.0000, 93, 1.53, 'My'), # Mega year - 94: Isotope(93.9063158, 0.1738, 94), - 95: Isotope(94.9080426, 0.1738, 95, 64.032, 'd'), - 96: Isotope(95.908276, 0.028, 96, 24.0, 'Ey')}), # Exa year - Element( - 41, 'Nb', 'Niobium', - group=5, period=5, block='d', series=8, - mass=92.90638, eleneg=1.6, eleaffin=0.893, - covrad=1.34, atmrad=2.08, vdwrad=0.0, - tboil=5015.0, tmelt=2742.0, density=8.58, - eleconfig='[Kr] 4d4 5s', - oxistates='5*, 3', - ionenergy=(6.7589, 14.32, 25.04, 38.3, 50.55, - 102.6, 125.0, ), - isotopes={93: Isotope(92.9063775, 1.0, 93), - 94: Isotope(93.9072839, 0.0, 94), - 95: Isotope(94.9068358, 0.0, 95, 34.991, 's')}), - Element( - 42, 'Mo', 'Molybdenum', - group=6, period=5, block='d', series=8, - mass=95.94, eleneg=2.16, eleaffin=0.7472, - covrad=1.3, atmrad=2.01, vdwrad=0.0, - tboil=4912.0, tmelt=2896.0, density=10.28, - eleconfig='[Kr] 4d5 5s', - oxistates='6*, 5, 4, 3, 2, 0', - ionenergy=(7.0924, 16.15, 27.16, 46.4, 61.2, - 68.0, 126.8, 153.0, ), - isotopes={92: Isotope(91.90681, 0.1484, 92), - 94: Isotope(93.9050876, 0.0925, 94), - 95: Isotope(94.9058415, 0.1592, 95), - 96: Isotope(95.9046789, 0.1668, 96), - 97: Isotope(96.906021, 0.0955, 97), - 98: Isotope(97.9054078, 0.2413, 98), - 99: Isotope(98.9077119, 0.0000, 99, 65.94, 'h'), - 100: Isotope(99.907477, 0.0963, 100, 8.5, 'Ey')}), - Element( - 43, 'Tc', 'Technetium', - group=7, period=5, block='d', series=8, - mass=97.907216, eleneg=1.9, eleaffin=0.55, - covrad=1.27, atmrad=1.95, vdwrad=0.0, - tboil=4538.0, tmelt=2477.0, density=11.49, - eleconfig='[Kr] 4d5 5s2', - oxistates='7*', - ionenergy=(7.28, 15.26, 29.54, ), - isotopes={98: Isotope(97.907216, 1.0, 98, 4.2, 'My'), - 99: Isotope(98.9062547, 0.0, 99, 211.1, 'ky')}), - Element( - 44, 'Ru', 'Ruthenium', - group=8, period=5, block='d', series=8, - mass=101.07, eleneg=2.2, eleaffin=1.04638, - covrad=1.25, atmrad=1.89, vdwrad=0.0, - tboil=4425.0, tmelt=2610.0, density=12.45, - eleconfig='[Kr] 4d7 5s', - oxistates='8, 6, 4*, 3*, 2, 0, -2', - ionenergy=(7.3605, 16.76, 28.47, ), - isotopes={96: Isotope(95.907598, 0.0554, 96), - 98: Isotope(97.905287, 0.0187, 98), - 99: Isotope(98.9059393, 0.1276, 99), - 100: Isotope(99.9042197, 0.126, 100), - 101: Isotope(100.9055822, 0.1706, 101), - 102: Isotope(101.9043495, 0.3155, 102), - 103: Isotope(102.9063238, 0.0000, 103, 39.26, 's'), - 104: Isotope(103.90543, 0.1862, 104), - 106: Isotope(105.9073294, 0.0000, 106, 373.59, 'd')}), - Element( - 45, 'Rh', 'Rhodium', - group=9, period=5, block='d', series=8, - mass=102.9055, eleneg=2.28, eleaffin=1.14289, - covrad=1.25, atmrad=1.83, vdwrad=0.0, - tboil=3970.0, tmelt=2236.0, density=12.41, - eleconfig='[Kr] 4d8 5s', - oxistates='5, 4, 3*, 1*, 2, 0', - ionenergy=(7.4589, 18.08, 31.06, ), - isotopes={102: Isotope(101.9068432, 0.0, 102, 207.0, 'd'), - 103: Isotope(102.905504, 1.0, 103), - 106: Isotope(105.9072871, 0.0, 106, 29.8, 's')}), # second - Element( - 46, 'Pd', 'Palladium', - group=10, period=5, block='d', series=8, - mass=106.42, eleneg=2.2, eleaffin=0.56214, - covrad=1.28, atmrad=1.79, vdwrad=1.63, - tboil=3240.0, tmelt=1825.0, density=12.02, - eleconfig='[Kr] 4d10', - oxistates='4, 2*, 0', - ionenergy=(8.3369, 19.43, 32.93, ), - isotopes={102: Isotope(101.905608, 0.0102, 102), - 104: Isotope(103.904035, 0.1114, 104), - 105: Isotope(104.905084, 0.2233, 105), - 106: Isotope(105.903483, 0.2733, 106), - 107: Isotope(106.9051335, 0.0000, 107, 6.5, 'My'), - 108: Isotope(107.903894, 0.2646, 108), - 110: Isotope(109.905152, 0.1172, 110)}), - Element( - 47, 'Ag', 'Silver', - group=11, period=5, block='d', series=8, - mass=107.8682, eleneg=1.93, eleaffin=1.30447, - covrad=1.34, atmrad=1.75, vdwrad=1.72, - tboil=2436.0, tmelt=1235.1, density=10.49, - eleconfig='[Kr] 4d10 5s', - oxistates='2, 1*', - ionenergy=(7.5762, 21.49, 34.83, ), - isotopes={107: Isotope(106.905093, 0.51839, 107), - 109: Isotope(108.904756, 0.48161, 109), - 110: Isotope(109.9061072, 0.00000, 110, 24.6, 's')}), - Element( - 48, 'Cd', 'Cadmium', - group=12, period=5, block='d', series=8, - mass=112.411, eleneg=1.69, eleaffin=0.0, - covrad=1.48, atmrad=1.71, vdwrad=1.58, - tboil=1040.0, tmelt=594.26, density=8.64, - eleconfig='[Kr] 4d10 5s2', - oxistates='2*', - ionenergy=(8.9938, 16.908, 37.48, ), - isotopes={106: Isotope(105.906458, 0.0125, 106), - 108: Isotope(107.904183, 0.0089, 108), - 109: Isotope(108.9049823, 0.00, 109), - 110: Isotope(109.903006, 0.1249, 110), - 111: Isotope(110.904182, 0.128, 111), - 112: Isotope(111.9027572, 0.2413, 112), - # Peta year - 113: Isotope(112.9044009, 0.1222, 113, 7.7, 'Py'), - 114: Isotope(113.9033581, 0.2873, 114), - 115: Isotope(114.9054310, 0.0000, 115, 53.46, 'h'), - 116: Isotope(115.904755, 0.0749, 116, 30.0, 'Ey')}), # Exa year - Element( - 49, 'In', 'Indium', - group=13, period=5, block='p', series=7, - mass=114.818, eleneg=1.78, eleaffin=0.404, - covrad=1.44, atmrad=2.0, vdwrad=1.93, - tboil=2350.0, tmelt=429.78, density=7.31, - eleconfig='[Kr] 4d10 5s2 5p', - oxistates='3*', - ionenergy=(5.7864, 18.869, 28.03, 28.03, ), - isotopes={113: Isotope(112.904061, 0.0429, 113), - 115: Isotope(114.903878, 0.9571, 115, 441.0, 'Ty')}), # Tera year - Element( - 50, 'Sn', 'Tin', - group=14, period=5, block='p', series=7, - mass=118.71, eleneg=1.96, eleaffin=1.112066, - covrad=1.41, atmrad=1.72, vdwrad=2.17, - tboil=2876.0, tmelt=505.12, density=7.29, - eleconfig='[Kr] 4d10 5s2 5p2', - oxistates='4*, 2*', - ionenergy=(7.3439, 14.632, 30.502, 40.734, 72.28, ), - isotopes={112: Isotope(111.904821, 0.0097, 112), - 114: Isotope(113.902782, 0.0066, 114), - 115: Isotope(114.903346, 0.0034, 115), - 116: Isotope(115.901744, 0.1454, 116), - 117: Isotope(116.902954, 0.0768, 117), - 118: Isotope(117.901606, 0.2422, 118), - 119: Isotope(118.903309, 0.0859, 119), - 120: Isotope(119.9021966, 0.3258, 120), - 121: Isotope(120.9042355, 0.0000, 121, 27.03, 'h'), - 122: Isotope(121.9034401, 0.0463, 122), - 123: Isotope(122.9057208, 0.0463, 123, 129.2, 'd'), - 124: Isotope(123.9052746, 0.0579, 124), - 125: Isotope(124.9077841, 0.0000, 125, 9.64, 'd'), - 126: Isotope(125.907653, 0.0000, 126, 230.0, 'ky')}), # kilo year - Element( - 51, 'Sb', 'Antimony', - group=15, period=5, block='p', series=5, - mass=121.76, eleneg=2.05, eleaffin=1.047401, - covrad=1.4, atmrad=1.53, vdwrad=0.0, - tboil=1860.0, tmelt=903.91, density=6.69, - eleconfig='[Kr] 4d10 5s2 5p3', - oxistates='5, 3*, -3', - ionenergy=(8.6084, 16.53, 25.3, 44.2, 56.0, - 108.0, ), - isotopes={121: Isotope(120.903818, 0.5721, 121), - 122: Isotope(121.9051737, 0.0000, 122, 2.7238, 'd'), - 123: Isotope(122.9042157, 0.4279, 123), - 124: Isotope(123.9059357, 0.0000, 124, 60.2, 'd'), - 125: Isotope(124.9052538, 0.0000, 125, 2.75856, 'y'), - 126: Isotope(125.907247, 0.0000, 126, 12.35, 'd')}), - Element( - 52, 'Te', 'Tellurium', - group=16, period=5, block='p', series=5, - mass=127.6, eleneg=2.1, eleaffin=1.970875, - covrad=1.36, atmrad=1.42, vdwrad=2.06, - tboil=1261.0, tmelt=722.72, density=6.25, - eleconfig='[Kr] 4d10 5s2 5p4', - oxistates='6, 4*, -2', - ionenergy=(9.0096, 18.6, 27.96, 37.41, 58.75, - 70.7, 137.0, ), - isotopes={120: Isotope(119.90402, 0.0009, 120), - 121: Isotope(120.904936, 0.0000, 121, 19.16, 'd'), - 122: Isotope(121.9030471, 0.0255, 122), - # >600 Tera year - 123: Isotope(122.904273, 0.0089, 123, 600.0, 'Ty'), - 124: Isotope(123.9028195, 0.0474, 124), - 125: Isotope(124.9044247, 0.0707, 125), - 126: Isotope(125.9033055, 0.1884, 126), - 127: Isotope(126.9052263, 0.0000, 127, 9.35, 'h'), - # Yotta year - 128: Isotope(127.9044614, 0.3174, 128, 2.2, 'Yy'), - 129: Isotope(128.9062244, 0.0000, 129, 69.6, 'm'), # minute - 130: Isotope(129.9062228, 0.3408, 130, 790.0, 'Ey')}), # Exa year - Element( - 53, 'I', 'Iodine', - group=17, period=5, block='p', series=6, - mass=126.90447, eleneg=2.66, eleaffin=3.059038, - covrad=1.33, atmrad=1.32, vdwrad=1.98, - tboil=457.5, tmelt=386.7, density=4.94, - eleconfig='[Kr] 4d10 5s2 5p5', - oxistates='7, 5, 1, -1*', - ionenergy=(10.4513, 19.131, 33.0, ), - isotopes={127: Isotope(126.904468, 1.0, 127), - 129: Isotope(128.9049877, 0.0, 129, 15.7, 'My')}), # Mega year - Element( - 54, 'Xe', 'Xenon', - group=18, period=5, block='p', series=2, - mass=131.293, eleneg=0.0, eleaffin=0.0, - covrad=1.31, atmrad=1.24, vdwrad=2.16, - tboil=165.1, tmelt=161.39, density=4.49, - eleconfig='[Kr] 4d10 5s2 5p6', - oxistates='2, 4, 6', - ionenergy=(12.1298, 21.21, 32.1, ), - isotopes={124: Isotope(123.9058958, 0.0009, 124), - 125: Isotope(124.9063955, 0.000, 125, 16.9, 'h'), - 126: Isotope(125.904269, 0.0009, 126), - 128: Isotope(127.9035304, 0.0192, 128), - 129: Isotope(128.9047795, 0.2644, 129), - 130: Isotope(129.9035079, 0.0408, 130), - 131: Isotope(130.9050819, 0.2118, 131), - 132: Isotope(131.9041545, 0.2689, 132), - 133: Isotope(132.9059107, 0.0000, 133, 5.2475, 'd'), - 134: Isotope(133.9053945, 0.1044, 134), - 135: Isotope(134.9072275, 0.0000, 135, 9.14, 'h'), - 136: Isotope(135.90722, 0.0887, 136)}), - Element( - 55, 'Cs', 'Caesium', - group=1, period=6, block='s', series=3, - mass=132.90545, eleneg=0.79, eleaffin=0.471626, - covrad=2.35, atmrad=3.34, vdwrad=0.0, - tboil=944.0, tmelt=301.54, density=1.9, - eleconfig='[Xe] 6s', - oxistates='1*', - ionenergy=(3.8939, 25.1, ), - isotopes={133: Isotope(132.905447, 1.0, 133), - 134: Isotope(133.906718475, 0.0, 134, 2.0648, 'y'), - # Mega year - 135: Isotope(134.9059770, 0.0, 135, 2.3, 'My'), - 136: Isotope(135.9073116, 0.0, 136, 13.16, 'd'), - 137: Isotope(136.90708947, 0.0, 137, 30.1671, 'y')}), - Element( - 56, 'Ba', 'Barium', - group=2, period=6, block='s', series=4, - mass=137.327, eleneg=0.89, eleaffin=0.14462, - covrad=1.98, atmrad=2.78, vdwrad=0.0, - tboil=2078.0, tmelt=1002.0, density=3.65, - eleconfig='[Xe] 6s2', - oxistates='2*', - ionenergy=(5.2117, 100.004, ), - isotopes={130: Isotope(129.90631, 0.00106, 130), - 132: Isotope(131.905056, 0.00101, 132), - 133: Isotope(132.9060075, 0.0, 133, 10.51, 'y'), - 134: Isotope(133.904503, 0.02417, 134), - 135: Isotope(134.905683, 0.06592, 135), - 136: Isotope(135.90457, 0.07854, 136), - 137: Isotope(136.905821, 0.11232, 137), - 138: Isotope(137.905241, 0.71698, 138)}), - Element( - 57, 'La', 'Lanthanum', - group=3, period=6, block='f', series=9, - mass=138.9055, eleneg=1.1, eleaffin=0.47, - covrad=1.69, atmrad=2.74, vdwrad=0.0, - tboil=3737.0, tmelt=1191.0, density=6.16, - eleconfig='[Xe] 5d 6s2', - oxistates='3*', - ionenergy=(5.5769, 11.06, 19.175, ), - isotopes={137: Isotope(136.906494, 0.0, 137), - # Giga year - 138: Isotope(137.9071119, 0.090, 138, 102.0, 'Gy'), - 139: Isotope(138.906348, 0.9991, 139)}), - Element( - 58, 'Ce', 'Cerium', - group=3, period=6, block='f', series=9, - mass=140.116, eleneg=1.12, eleaffin=0.5, - covrad=1.65, atmrad=2.7, vdwrad=0.0, - tboil=3715.0, tmelt=1071.0, density=6.77, - eleconfig='[Xe] 4f 5d 6s2', - oxistates='4, 3*', - ionenergy=(5.5387, 10.85, 20.2, 36.72, ), - isotopes={136: Isotope(135.90714, 0.00185, 136), - 138: Isotope(137.905986, 0.00251, 138), - 139: Isotope(138.9066527, 0.00000, 139, 137.641, 'd'), - 140: Isotope(139.905434, 0.8845, 140), - 141: Isotope(140.9082763, 0.0000, 141, 32.508, 'd'), - 142: Isotope(141.90924, 0.11114, 142), - 144: Isotope(143.9136473, 0.00000, 144, 284.91, 'd')}), - Element( - 59, 'Pr', 'Praseodymium', - group=3, period=6, block='f', series=9, - mass=140.90765, eleneg=1.13, eleaffin=0.5, - covrad=1.65, atmrad=2.67, vdwrad=0.0, - tboil=3785.0, tmelt=1204.0, density=6.48, - eleconfig='[Xe] 4f3 6s2', - oxistates='4, 3*', - ionenergy=(5.473, 10.55, 21.62, 38.95, 57.45, ), - isotopes={141: Isotope(140.907648, 1.0, 141), - 144: Isotope(143.9133052, 0.0, 144, 17.28, 'm')}), # minute - Element( - 60, 'Nd', 'Neodymium', - group=3, period=6, block='f', series=9, - mass=144.24, eleneg=1.14, eleaffin=0.5, - covrad=1.64, atmrad=2.64, vdwrad=0.0, - tboil=3347.0, tmelt=1294.0, density=7.0, - eleconfig='[Xe] 4f4 6s2', - oxistates='3*', - ionenergy=(5.525, 10.72, ), - isotopes={142: Isotope(141.907719, 0.272, 142), - 143: Isotope(142.90981, 0.122, 143), - # Peta year - 144: Isotope(143.910083, 0.238, 144, 2.29, 'Py'), - 145: Isotope(144.912569, 0.083, 145), - 146: Isotope(145.913112, 0.172, 146), - 147: Isotope(146.9161004, 0.000, 147, 10.98, 'd'), # day - 148: Isotope(147.916889, 0.057, 148), - 150: Isotope(149.9208909, 0.056, 150, 6.7, 'Ey')}), # Exa year - Element( - 61, 'Pm', 'Promethium', - group=3, period=6, block='f', series=9, - mass=144.912744, eleneg=1.13, eleaffin=0.5, - covrad=1.63, atmrad=2.62, vdwrad=0.0, - tboil=3273.0, tmelt=1315.0, density=7.22, - eleconfig='[Xe] 4f5 6s2', - oxistates='3*', - ionenergy=(5.582, 10.9, ), - isotopes={145: Isotope(144.9127490, 0.0, 145, 17.7, 'y'), - 146: Isotope(145.9146963, 0.0, 146, 5.53, 'y'), - 147: Isotope(146.9151385, 0.0, 147, 2.6234, 'y'), - 148: Isotope(147.9174746, 0.0, 148, 5.368, 'd')}), - Element( - 62, 'Sm', 'Samarium', - group=3, period=6, block='f', series=9, - mass=150.36, eleneg=1.17, eleaffin=0.5, - covrad=1.62, atmrad=2.59, vdwrad=0.0, - tboil=2067.0, tmelt=1347.0, density=7.54, - eleconfig='[Xe] 4f6 6s2', - oxistates='3*, 2', - ionenergy=(5.6437, 11.07, ), - isotopes={144: Isotope(143.911995, 0.0307, 144), - 146: Isotope(145.9130409, 0.0, 146, 103.0, 'My'), - 147: Isotope(146.9148979, 0.1499, 147, 106.0, 'Gy'), - 148: Isotope(147.914818, 0.1124, 148, 7.0, 'Py'), - 149: Isotope(148.91718, 0.1382, 149), - 150: Isotope(149.917271, 0.0738, 150), - 151: Isotope(150.9199324, 0.0000, 151, 90.0, 'y'), - 152: Isotope(151.919728, 0.2675, 152), - 154: Isotope(153.922205, 0.2275, 154)}), - Element( - 63, 'Eu', 'Europium', - group=3, period=6, block='f', series=9, - mass=151.964, eleneg=1.2, eleaffin=0.5, - covrad=1.85, atmrad=2.56, vdwrad=0.0, - tboil=1800.0, tmelt=1095.0, density=5.25, - eleconfig='[Xe] 4f7 6s2', - oxistates='3*, 2', - ionenergy=(5.6704, 11.25, ), - isotopes={151: Isotope(150.919846, 0.4781, 151), - 152: Isotope(151.9217445, 0.0000, 152, 13.537, 'y'), - 153: Isotope(152.921226, 0.5219, 153), - 154: Isotope(153.9229792, 0.0000, 154, 8.593, 'y'), - 155: Isotope(154.9228933, 0.0000, 155, 4.7611, 'y')}), - Element( - 64, 'Gd', 'Gadolinium', - group=3, period=6, block='f', series=9, - mass=157.25, eleneg=1.2, eleaffin=0.5, - covrad=1.61, atmrad=2.54, vdwrad=0.0, - tboil=3545.0, tmelt=1585.0, density=7.89, - eleconfig='[Xe] 4f7 5d 6s2', - oxistates='3*', - ionenergy=(6.1498, 12.1, ), - isotopes={152: Isotope(151.919788, 0.002, 152, 108.0, 'Ty'), - 153: Isotope(152.9217495, 0.0, 153, 240.4, 'd'), - 154: Isotope(153.920862, 0.0218, 154), - 155: Isotope(154.922619, 0.148, 155), - 156: Isotope(155.92212, 0.2047, 156), - 157: Isotope(156.923957, 0.1565, 157), - 158: Isotope(157.924101, 0.2484, 158), - 159: Isotope(158.9263887, 0.0000, 159, 18.479, 'h'), - 160: Isotope(159.927051, 0.2186, 160)}), - Element( - 65, 'Tb', 'Terbium', - group=3, period=6, block='f', series=9, - mass=158.92534, eleneg=1.2, eleaffin=0.5, - covrad=1.59, atmrad=2.51, vdwrad=0.0, - tboil=3500.0, tmelt=1629.0, density=8.25, - eleconfig='[Xe] 4f9 6s2', - oxistates='4, 3*', - ionenergy=(5.8638, 11.52, ), - isotopes={158: Isotope(157.9254131, 0.0, 158, 180.0, 'y'), - 159: Isotope(158.925343, 1.0, 159), - 160: Isotope(159.9271676, 0.0, 160, 72.3, 'd')}), - Element( - 66, 'Dy', 'Dysprosium', - group=3, period=6, block='f', series=9, - mass=162.5, eleneg=1.22, eleaffin=0.5, - covrad=1.59, atmrad=2.49, vdwrad=0.0, - tboil=2840.0, tmelt=1685.0, density=8.56, - eleconfig='[Xe] 4f10 6s2', - oxistates='3*', - ionenergy=(5.9389, 11.67, ), - isotopes={156: Isotope(155.924278, 0.0006, 156), - 158: Isotope(157.924405, 0.001, 158), - 160: Isotope(159.925194, 0.0234, 160), - 161: Isotope(160.92693, 0.1891, 161), - 162: Isotope(161.926795, 0.2551, 162), - 163: Isotope(162.928728, 0.249, 163), - 164: Isotope(163.929171, 0.2818, 164)}), - Element( - 67, 'Ho', 'Holmium', - group=3, period=6, block='f', series=9, - mass=164.93032, eleneg=1.23, eleaffin=0.5, - covrad=1.58, atmrad=2.47, vdwrad=0.0, - tboil=2968.0, tmelt=1747.0, density=8.78, - eleconfig='[Xe] 4f11 6s2', - oxistates='3*', - ionenergy=(6.0215, 11.8, ), - isotopes={165: Isotope(164.930319, 1.0, 165), - 166: Isotope(165.9322842, 0.0, 166)}), - Element( - 68, 'Er', 'Erbium', - group=3, period=6, block='f', series=9, - mass=167.259, eleneg=1.24, eleaffin=0.5, - covrad=1.57, atmrad=2.45, vdwrad=0.0, - tboil=3140.0, tmelt=1802.0, density=9.05, - eleconfig='[Xe] 4f12 6s2', - oxistates='3*', - ionenergy=(6.1077, 11.93, ), - isotopes={162: Isotope(161.928775, 0.0014, 162), - 164: Isotope(163.929197, 0.0161, 164), - 166: Isotope(165.93029, 0.3361, 166), - 167: Isotope(166.932045, 0.2293, 167), - 168: Isotope(167.932368, 0.2678, 168), - 170: Isotope(169.93546, 0.1493, 170)}), - Element( - 69, 'Tm', 'Thulium', - group=3, period=6, block='f', series=9, - mass=168.93421, eleneg=1.25, eleaffin=0.5, - covrad=1.56, atmrad=2.42, vdwrad=0.0, - tboil=2223.0, tmelt=1818.0, density=9.32, - eleconfig='[Xe] 4f13 6s2', - oxistates='3*, 2', - ionenergy=(6.1843, 12.05, 23.71, ), - isotopes={169: Isotope(168.934211, 1.0, 169), - 170: Isotope(169.9358014, 0.0, 170), - 171: Isotope(170.9364294, 0.0, 171)}), - Element( - 70, 'Yb', 'Ytterbium', - group=3, period=6, block='f', series=9, - mass=173.04, eleneg=1.1, eleaffin=0.5, - covrad=1.74, atmrad=2.4, vdwrad=0.0, - tboil=1469.0, tmelt=1092.0, density=9.32, - eleconfig='[Xe] 4f14 6s2', - oxistates='3*, 2', - ionenergy=(6.2542, 12.17, 25.2, ), - isotopes={168: Isotope(167.933894, 0.0013, 168), - 170: Isotope(169.934759, 0.0304, 170), - 171: Isotope(170.936322, 0.1428, 171), - 172: Isotope(171.9363777, 0.2183, 172), - 173: Isotope(172.9382068, 0.1613, 173), - 174: Isotope(173.9388581, 0.3183, 174), - 176: Isotope(175.942568, 0.1276, 176)}), - Element( - 71, 'Lu', 'Lutetium', - group=3, period=6, block='d', series=9, - mass=174.967, eleneg=1.27, eleaffin=0.5, - covrad=1.56, atmrad=2.25, vdwrad=0.0, - tboil=3668.0, tmelt=1936.0, density=9.84, - eleconfig='[Xe] 4f14 5d 6s2', - oxistates='3*', - ionenergy=(5.4259, 13.9, ), - isotopes={175: Isotope(174.9407679, 0.9741, 175), - 176: Isotope(175.9426824, 0.0259, 176)}), - Element( - 72, 'Hf', 'Hafnium', - group=4, period=6, block='d', series=8, - mass=178.49, eleneg=1.3, eleaffin=0.0, - covrad=1.44, atmrad=2.16, vdwrad=0.0, - tboil=4875.0, tmelt=2504.0, density=13.31, - eleconfig='[Xe] 4f14 5d2 6s2', - oxistates='4*', - ionenergy=(6.8251, 14.9, 23.3, 33.3, ), - isotopes={174: Isotope(173.94004, 0.0016, 174), - 176: Isotope(175.9414018, 0.0526, 176), - 177: Isotope(176.94322, 0.186, 177), - 178: Isotope(177.9436977, 0.2728, 178), - 179: Isotope(178.9458151, 0.1362, 179), - 180: Isotope(179.9465488, 0.3508, 180)}), - Element( - 73, 'Ta', 'Tantalum', - group=5, period=6, block='d', series=8, - mass=180.9479, eleneg=1.5, eleaffin=0.322, - covrad=1.34, atmrad=2.09, vdwrad=0.0, - tboil=5730.0, tmelt=3293.0, density=16.68, - eleconfig='[Xe] 4f14 5d3 6s2', - oxistates='5*', - ionenergy=(7.5496, ), - isotopes={180: Isotope(179.947466, 0.00012, 180), - 181: Isotope(180.947996, 0.99988, 181)}), - Element( - 74, 'W', 'Tungsten', - group=6, period=6, block='d', series=8, - mass=183.84, eleneg=2.36, eleaffin=0.815, - covrad=1.3, atmrad=2.02, vdwrad=0.0, - tboil=5825.0, tmelt=3695.0, density=19.26, - eleconfig='[Xe] 4f14 5d4 6s2', - oxistates='6*, 5, 4, 3, 2, 0', - ionenergy=(7.864, ), - isotopes={180: Isotope(179.946706, 0.0012, 180), - 182: Isotope(181.948206, 0.265, 182), - 183: Isotope(182.9502245, 0.1431, 183), - 184: Isotope(183.9509326, 0.3064, 184), - 186: Isotope(185.954362, 0.2843, 186)}), - Element( - 75, 'Re', 'Rhenium', - group=7, period=6, block='d', series=8, - mass=186.207, eleneg=1.9, eleaffin=0.15, - covrad=1.28, atmrad=1.97, vdwrad=0.0, - tboil=5870.0, tmelt=3455.0, density=21.03, - eleconfig='[Xe] 4f14 5d5 6s2', - oxistates='7, 6, 4, 2, -1', - ionenergy=(7.8335, ), - isotopes={185: Isotope(184.9529557, 0.374, 185), - 187: Isotope(186.9557508, 0.626, 187)}), - Element( - 76, 'Os', 'Osmium', - group=8, period=6, block='d', series=8, - mass=190.23, eleneg=2.2, eleaffin=1.0778, - covrad=1.26, atmrad=1.92, vdwrad=0.0, - tboil=5300.0, tmelt=3300.0, density=22.61, - eleconfig='[Xe] 4f14 5d6 6s2', - oxistates='8, 6, 4*, 3, 2, 0, -2', - ionenergy=(8.4382, ), - isotopes={184: Isotope(183.952491, 0.0002, 184), - 186: Isotope(185.953838, 0.0159, 186), - 187: Isotope(186.9557479, 0.0196, 187), - 188: Isotope(187.955836, 0.1324, 188), - 189: Isotope(188.9581449, 0.1615, 189), - 190: Isotope(189.958445, 0.2626, 190), - 192: Isotope(191.961479, 0.4078, 192)}), - Element( - 77, 'Ir', 'Iridium', - group=9, period=6, block='d', series=8, - mass=192.217, eleneg=2.2, eleaffin=1.56436, - covrad=1.27, atmrad=1.87, vdwrad=0.0, - tboil=4700.0, tmelt=2720.0, density=22.65, - eleconfig='[Xe] 4f14 5d7 6s2', - oxistates='6, 4*, 3, 2, 1*, 0, -1', - ionenergy=(8.967, ), - isotopes={191: Isotope(190.960591, 0.373, 191), - 193: Isotope(192.962924, 0.627, 193)}), - Element( - 78, 'Pt', 'Platinum', - group=10, period=6, block='d', series=8, - mass=195.078, eleneg=2.28, eleaffin=2.1251, - covrad=1.3, atmrad=1.83, vdwrad=1.75, - tboil=4100.0, tmelt=2042.1, density=21.45, - eleconfig='[Xe] 4f14 5d9 6s', - oxistates='4*, 2*, 0', - ionenergy=(8.9588, 18.563, ), - isotopes={190: Isotope(189.95993, 0.00014, 190), - 192: Isotope(191.961035, 0.00782, 192), - 194: Isotope(193.962664, 0.32967, 194), - 195: Isotope(194.964774, 0.33832, 195), - 196: Isotope(195.964935, 0.25242, 196), - 198: Isotope(197.967876, 0.07163, 198)}), - Element( - 79, 'Au', 'Gold', - group=11, period=6, block='d', series=8, - mass=196.96655, eleneg=2.54, eleaffin=2.30861, - covrad=1.34, atmrad=1.79, vdwrad=1.66, - tboil=3130.0, tmelt=1337.58, density=19.32, - eleconfig='[Xe] 4f14 5d10 6s', - oxistates='3*, 1', - ionenergy=(9.2255, 20.5, ), - isotopes={197: Isotope(196.966552, 1.0, 197)}), - Element( - 80, 'Hg', 'Mercury', - group=12, period=6, block='d', series=8, - mass=200.59, eleneg=2.0, eleaffin=0.0, - covrad=1.49, atmrad=1.76, vdwrad=0.0, - tboil=629.88, tmelt=234.31, density=13.55, - eleconfig='[Xe] 4f14 5d10 6s2', - oxistates='2*, 1', - ionenergy=(10.4375, 18.756, 34.2, ), - isotopes={196: Isotope(195.965815, 0.0015, 196), - 198: Isotope(197.966752, 0.0997, 198), - 199: Isotope(198.968262, 0.1687, 199), - 200: Isotope(199.968309, 0.231, 200), - 201: Isotope(200.970285, 0.1318, 201), - 202: Isotope(201.970626, 0.2986, 202), - 204: Isotope(203.973476, 0.0687, 204)}), - Element( - 81, 'Tl', 'Thallium', - group=13, period=6, block='p', series=7, - mass=204.3833, eleneg=2.04, eleaffin=0.377, - covrad=1.48, atmrad=2.08, vdwrad=1.96, - tboil=1746.0, tmelt=577.0, density=11.85, - eleconfig='[Xe] 4f14 5d10 6s2 6p', - oxistates='3, 1*', - ionenergy=(6.1082, 20.428, 29.83, ), - isotopes={203: Isotope(202.972329, 0.29524, 203), - 205: Isotope(204.974412, 0.70476, 205)}), - Element( - 82, 'Pb', 'Lead', - group=14, period=6, block='p', series=7, - mass=207.2, eleneg=2.33, eleaffin=0.364, - covrad=1.47, atmrad=1.81, vdwrad=2.02, - tboil=2023.0, tmelt=600.65, density=11.34, - eleconfig='[Xe] 4f14 5d10 6s2 6p2', - oxistates='4, 2*', - ionenergy=(7.4167, 15.032, 31.937, 42.32, 68.8, ), - isotopes={204: Isotope(203.973029, 0.014, 204), - 206: Isotope(205.974449, 0.241, 206), - 207: Isotope(206.975881, 0.221, 207), - 208: Isotope(207.976636, 0.524, 208)}), - Element( - 83, 'Bi', 'Bismuth', - group=15, period=6, block='p', series=7, - mass=208.98038, eleneg=2.02, eleaffin=0.942363, - covrad=1.46, atmrad=1.63, vdwrad=0.0, - tboil=1837.0, tmelt=544.59, density=9.8, - eleconfig='[Xe] 4f14 5d10 6s2 6p3', - oxistates='5, 3*', - ionenergy=(7.2855, 16.69, 25.56, 45.3, 56.0, - 88.3, ), - isotopes={209: Isotope(208.980383, 1.0, 209)}), - Element( - 84, 'Po', 'Polonium', - group=16, period=6, block='p', series=5, - mass=208.982416, eleneg=2.0, eleaffin=1.9, - covrad=1.46, atmrad=1.53, vdwrad=0.0, - tboil=0.0, tmelt=527.0, density=9.2, - eleconfig='[Xe] 4f14 5d10 6s2 6p4', - oxistates='6, 4*, 2', - ionenergy=(8.414, ), - isotopes={209: Isotope(208.982416, 1.0, 209)}), - Element( - 85, 'At', 'Astatine', - group=17, period=6, block='p', series=6, - mass=209.9871, eleneg=2.2, eleaffin=2.8, - covrad=1.45, atmrad=1.43, vdwrad=0.0, - tboil=610.0, tmelt=575.0, density=0.0, - eleconfig='[Xe] 4f14 5d10 6s2 6p5', - oxistates='7, 5, 3, 1, -1*', + 1, + "H", + "Hydrogen", + group=1, + period=1, + block="s", + series=1, + mass=1.00794, + eleneg=2.2, + eleaffin=0.75420375, + covrad=0.32, + atmrad=0.79, + vdwrad=1.2, + tboil=20.28, + tmelt=13.81, + density=0.084, + eleconfig="1s", + oxistates="1*, -1", + ionenergy=(13.5984,), + isotopes={ + 1: Isotope(1.0078250321, 0.999885, 1), + 2: Isotope(2.014101778, 0.000115, 2), + 3: Isotope(3.0160492777, 0.000, 3, 12.32, "y"), + }, + ), # y -> year + Element( + 2, + "He", + "Helium", + group=18, + period=1, + block="s", + series=2, + mass=4.002602, + eleneg=0.0, + eleaffin=0.0, + covrad=0.93, + atmrad=0.49, + vdwrad=1.4, + tboil=4.216, + tmelt=0.95, + density=0.1785, + eleconfig="1s2", + oxistates="*", + ionenergy=( + 24.5874, + 54.416, + ), + isotopes={ + 3: Isotope(3.0160293097, 1.37e-06, 3), + 4: Isotope(4.0026032497, 0.99999863, 4), + }, + ), + Element( + 3, + "Li", + "Lithium", + group=1, + period=2, + block="s", + series=3, + mass=6.941, + eleneg=0.98, + eleaffin=0.618049, + covrad=1.23, + atmrad=2.05, + vdwrad=1.82, + tboil=1615.0, + tmelt=453.7, + density=0.53, + eleconfig="[He] 2s", + oxistates="1*", + ionenergy=( + 5.3917, + 75.638, + 122.451, + ), + isotopes={6: Isotope(6.0151223, 0.0759, 6), 7: Isotope(7.016004, 0.9241, 7)}, + ), + Element( + 4, + "Be", + "Beryllium", + group=2, + period=2, + block="s", + series=4, + mass=9.012182, + eleneg=1.57, + eleaffin=0.0, + covrad=0.9, + atmrad=1.4, + vdwrad=0.0, + tboil=3243.0, + tmelt=1560.0, + density=1.85, + eleconfig="[He] 2s2", + oxistates="2*", + ionenergy=( + 9.3227, + 18.211, + 153.893, + 217.713, + ), + isotopes={ + 9: Isotope(9.0121821, 1.0, 9), + 10: Isotope(10.01353382, 0.0, 10, 1.51, "My"), + }, + ), # My -> Mega year + Element( + 5, + "B", + "Boron", + group=13, + period=2, + block="p", + series=5, + mass=10.811, + eleneg=2.04, + eleaffin=0.279723, + covrad=0.82, + atmrad=1.17, + vdwrad=0.0, + tboil=4275.0, + tmelt=2365.0, + density=2.46, + eleconfig="[He] 2s2 2p", + oxistates="3*", + ionenergy=( + 8.298, + 25.154, + 37.93, + 59.368, + 340.217, + ), + isotopes={ + 10: Isotope(10.012937, 0.199, 10), + 11: Isotope(11.0093055, 0.801, 11), + }, + ), + Element( + 6, + "C", + "Carbon", + group=14, + period=2, + block="p", + series=1, + mass=12.0107, + eleneg=2.55, + eleaffin=1.262118, + covrad=0.77, + atmrad=0.91, + vdwrad=1.7, + tboil=5100.0, + tmelt=3825.0, + density=3.51, + eleconfig="[He] 2s2 2p2", + oxistates="4*, 2, -4*", + ionenergy=( + 11.2603, + 24.383, + 47.877, + 64.492, + 392.077, + 489.981, + ), + isotopes={ + 12: Isotope(12.0, 0.9893, 12), + 13: Isotope(13.0033548378, 0.0107, 13), + 14: Isotope(14.0032419887, 0.00, 14), + }, + ), + Element( + 7, + "N", + "Nitrogen", + group=15, + period=2, + block="p", + series=1, + mass=14.0067, + eleneg=3.04, + eleaffin=-0.07, + covrad=0.75, + atmrad=0.75, + vdwrad=1.55, + tboil=77.344, + tmelt=63.15, + density=1.17, + eleconfig="[He] 2s2 2p3", + oxistates="5, 4, 3, 2, -3*", + ionenergy=( + 14.5341, + 39.601, + 47.488, + 77.472, + 97.888, + 522.057, + 667.029, + ), + isotopes={ + 14: Isotope(14.0030740052, 0.99632, 14), + 15: Isotope(15.0001088984, 0.00368, 15), + }, + ), + Element( + 8, + "O", + "Oxygen", + group=16, + period=2, + block="p", + series=1, + mass=15.9994, + eleneg=3.44, + eleaffin=1.461112, + covrad=0.73, + atmrad=0.65, + vdwrad=1.52, + tboil=90.188, + tmelt=54.8, + density=1.33, + eleconfig="[He] 2s2 2p4", + oxistates="-2*, -1", + ionenergy=( + 13.6181, + 35.116, + 54.934, + 54.934, + 77.412, + 113.896, + 138.116, + 739.315, + 871.387, + ), + isotopes={ + 16: Isotope(15.9949146221, 0.99757, 16), + 17: Isotope(16.9991315, 0.00038, 17), + 18: Isotope(17.9991604, 0.00205, 18), + }, + ), + Element( + 9, + "F", + "Fluorine", + group=17, + period=2, + block="p", + series=6, + mass=18.9984032, + eleneg=3.98, + eleaffin=3.4011887, + covrad=0.72, + atmrad=0.57, + vdwrad=1.47, + tboil=85.0, + tmelt=53.55, + density=1.58, + eleconfig="[He] 2s2 2p5", + oxistates="-1*", + ionenergy=( + 17.4228, + 34.97, + 62.707, + 87.138, + 114.24, + 157.161, + 185.182, + 953.886, + 1103.089, + ), + isotopes={19: Isotope(18.9984032, 1.0, 19)}, + ), + Element( + 10, + "Ne", + "Neon", + group=18, + period=2, + block="p", + series=2, + mass=20.1797, + eleneg=0.0, + eleaffin=0.0, + covrad=0.71, + atmrad=0.51, + vdwrad=1.54, + tboil=27.1, + tmelt=24.55, + density=0.8999, + eleconfig="[He] 2s2 2p6", + oxistates="*", + ionenergy=( + 21.5645, + 40.962, + 63.45, + 97.11, + 126.21, + 157.93, + 207.27, + 239.09, + 1195.797, + 1362.164, + ), + isotopes={ + 20: Isotope(19.9924401759, 0.9048, 20), + 21: Isotope(20.99384674, 0.0027, 21), + 22: Isotope(21.99138551, 0.0925, 22), + }, + ), + Element( + 11, + "Na", + "Sodium", + group=1, + period=3, + block="s", + series=3, + mass=22.98977, + eleneg=0.93, + eleaffin=0.547926, + covrad=1.54, + atmrad=2.23, + vdwrad=2.27, + tboil=1156.0, + tmelt=371.0, + density=0.97, + eleconfig="[Ne] 3s", + oxistates="1*", + ionenergy=( + 5.1391, + 47.286, + 71.64, + 98.91, + 138.39, + 172.15, + 208.47, + 264.18, + 299.87, + 1465.091, + 1648.659, + ), + isotopes={23: Isotope(22.98976967, 1.0, 23)}, + ), + Element( + 12, + "Mg", + "Magnesium", + group=2, + period=3, + block="s", + series=4, + mass=24.305, + eleneg=1.31, + eleaffin=0.0, + covrad=1.36, + atmrad=1.72, + vdwrad=1.73, + tboil=1380.0, + tmelt=922.0, + density=1.74, + eleconfig="[Ne] 3s2", + oxistates="2*", + ionenergy=( + 7.6462, + 15.035, + 80.143, + 109.24, + 141.26, + 186.5, + 224.94, + 265.9, + 327.95, + 367.53, + 1761.802, + 1962.613, + ), + isotopes={ + 24: Isotope(23.9850419, 0.7899, 24), + 25: Isotope(24.98583702, 0.1, 25), + 26: Isotope(25.98259304, 0.1101, 26), + }, + ), + Element( + 13, + "Al", + "Aluminium", + group=13, + period=3, + block="p", + series=7, + mass=26.981538, + eleneg=1.61, + eleaffin=0.43283, + covrad=1.18, + atmrad=1.82, + vdwrad=0.0, + tboil=2740.0, + tmelt=933.5, + density=2.7, + eleconfig="[Ne] 3s2 3p", + oxistates="3*", + ionenergy=( + 5.9858, + 18.828, + 28.447, + 119.99, + 153.71, + 190.47, + 241.43, + 284.59, + 330.21, + 398.57, + 442.07, + 2085.983, + 2304.08, + ), + isotopes={27: Isotope(26.98153844, 1.0, 27)}, + ), + Element( + 14, + "Si", + "Silicon", + group=14, + period=3, + block="p", + series=5, + mass=28.0855, + eleneg=1.9, + eleaffin=1.389521, + covrad=1.11, + atmrad=1.46, + vdwrad=2.1, + tboil=2630.0, + tmelt=1683.0, + density=2.33, + eleconfig="[Ne] 3s2 3p2", + oxistates="4*, -4", + ionenergy=( + 8.1517, + 16.345, + 33.492, + 45.141, + 166.77, + 205.05, + 246.52, + 303.17, + 351.1, + 401.43, + 476.06, + 523.5, + 2437.676, + 2673.108, + ), + isotopes={ + 28: Isotope(27.9769265327, 0.922297, 28), + 29: Isotope(28.97649472, 0.046832, 29), + 30: Isotope(29.97377022, 0.030871, 30), + }, + ), + Element( + 15, + "P", + "Phosphorus", + group=15, + period=3, + block="p", + series=1, + mass=30.973761, + eleneg=2.19, + eleaffin=0.7465, + covrad=1.06, + atmrad=1.23, + vdwrad=1.8, + tboil=553.0, + tmelt=317.3, + density=1.82, + eleconfig="[Ne] 3s2 3p3", + oxistates="5*, 3, -3", + ionenergy=( + 10.4867, + 19.725, + 30.18, + 51.37, + 65.023, + 220.43, + 263.22, + 309.41, + 371.73, + 424.5, + 479.57, + 560.41, + 611.85, + 2816.943, + 3069.762, + ), + isotopes={31: Isotope(30.97376151, 1.0, 31)}, + ), + Element( + 16, + "S", + "Sulfur", + group=16, + period=3, + block="p", + series=1, + mass=32.065, + eleneg=2.58, + eleaffin=2.0771029, + covrad=1.02, + atmrad=1.09, + vdwrad=1.8, + tboil=717.82, + tmelt=392.2, + density=2.06, + eleconfig="[Ne] 3s2 3p4", + oxistates="6*, 4, 2, -2", + ionenergy=( + 10.36, + 23.33, + 34.83, + 47.3, + 72.68, + 88.049, + 280.93, + 328.23, + 379.1, + 447.09, + 504.78, + 564.65, + 651.63, + 707.14, + 3223.836, + 3494.099, + ), + isotopes={ + 32: Isotope(31.97207069, 0.9493, 32), + 33: Isotope(32.9714585, 0.0076, 33), + 34: Isotope(33.96786683, 0.0429, 34), + 36: Isotope(35.96708088, 0.0002, 36), + }, + ), + Element( + 17, + "Cl", + "Chlorine", + group=17, + period=3, + block="p", + series=6, + mass=35.453, + eleneg=3.16, + eleaffin=3.612724, + covrad=0.99, + atmrad=0.97, + vdwrad=1.75, + tboil=239.18, + tmelt=172.17, + density=2.95, + eleconfig="[Ne] 3s2 3p5", + oxistates="7, 5, 3, 1, -1*", + ionenergy=( + 12.9676, + 23.81, + 39.61, + 53.46, + 67.8, + 98.03, + 114.193, + 348.28, + 400.05, + 455.62, + 529.97, + 591.97, + 656.69, + 749.75, + 809.39, + 3658.425, + 3946.193, + ), + isotopes={ + 35: Isotope(34.96885271, 0.7578, 35), + 37: Isotope(36.9659026, 0.2422, 37), + }, + ), + Element( + 18, + "Ar", + "Argon", + group=18, + period=3, + block="p", + series=2, + mass=39.948, + eleneg=0.0, + eleaffin=0.0, + covrad=0.98, + atmrad=0.88, + vdwrad=1.88, + tboil=87.45, + tmelt=83.95, + density=1.66, + eleconfig="[Ne] 3s2 3p6", + oxistates="*", + ionenergy=( + 15.7596, + 27.629, + 40.74, + 59.81, + 75.02, + 91.007, + 124.319, + 143.456, + 422.44, + 478.68, + 538.95, + 618.24, + 686.09, + 755.73, + 854.75, + 918.0, + 4120.778, + 4426.114, + ), + isotopes={ + 36: Isotope(35.96754628, 0.003365, 36), + 38: Isotope(37.9627322, 0.000632, 38), + 40: Isotope(39.962383123, 0.996003, 40), + }, + ), + Element( + 19, + "K", + "Potassium", + group=1, + period=4, + block="s", + series=3, + mass=39.0983, + eleneg=0.82, + eleaffin=0.501459, + covrad=2.03, + atmrad=2.77, + vdwrad=2.75, + tboil=1033.0, + tmelt=336.8, + density=0.86, + eleconfig="[Ar] 4s", + oxistates="1*", + ionenergy=( + 4.3407, + 31.625, + 45.72, + 60.91, + 82.66, + 100.0, + 117.56, + 154.86, + 175.814, + 503.44, + 564.13, + 629.09, + 714.02, + 787.13, + 861.77, + 968.0, + 1034.0, + 4610.955, + 4933.931, + ), + isotopes={ + 39: Isotope(38.9637069, 0.932581, 39), + 40: Isotope(39.96399867, 0.000117, 40), + 41: Isotope(40.96182597, 0.067302, 41), + }, + ), + Element( + 20, + "Ca", + "Calcium", + group=2, + period=4, + block="s", + series=4, + mass=40.078, + eleneg=1.0, + eleaffin=0.02455, + covrad=1.74, + atmrad=2.23, + vdwrad=0.0, + tboil=1757.0, + tmelt=1112.0, + density=1.54, + eleconfig="[Ar] 4s2", + oxistates="2*", + ionenergy=( + 6.1132, + 11.71, + 50.908, + 67.1, + 84.41, + 108.78, + 127.7, + 147.24, + 188.54, + 211.27, + 591.25, + 656.39, + 726.03, + 816.61, + 895.12, + 974.0, + 1087.0, + 1157.0, + 5129.045, + 5469.738, + ), + isotopes={ + 40: Isotope(39.9625912, 0.96941, 40), + 42: Isotope(41.9586183, 0.00647, 42), + 43: Isotope(42.9587668, 0.00135, 43), + 44: Isotope(43.9554811, 0.02086, 44), + 46: Isotope(45.9536928, 4e-05, 46), + 48: Isotope(47.952534, 0.00187, 48), + }, + ), + Element( + 21, + "Sc", + "Scandium", + group=3, + period=4, + block="d", + series=8, + mass=44.95591, + eleneg=1.36, + eleaffin=0.188, + covrad=1.44, + atmrad=2.09, + vdwrad=0.0, + tboil=3109.0, + tmelt=1814.0, + density=2.99, + eleconfig="[Ar] 3d 4s2", + oxistates="3*", + ionenergy=( + 6.5615, + 12.8, + 24.76, + 73.47, + 91.66, + 11.1, + 138.0, + 158.7, + 180.02, + 225.32, + 225.32, + 685.89, + 755.47, + 829.79, + 926.0, + ), + isotopes={45: Isotope(44.9559102, 1.0, 45)}, + ), + Element( + 22, + "Ti", + "Titanium", + group=4, + period=4, + block="d", + series=8, + mass=47.867, + eleneg=1.54, + eleaffin=0.084, + covrad=1.32, + atmrad=2.0, + vdwrad=0.0, + tboil=3560.0, + tmelt=1935.0, + density=4.51, + eleconfig="[Ar] 3d2 4s2", + oxistates="4*, 3", + ionenergy=( + 6.8281, + 13.58, + 27.491, + 43.266, + 99.22, + 119.36, + 140.8, + 168.5, + 193.5, + 193.2, + 215.91, + 265.23, + 291.497, + 787.33, + 861.33, + ), + isotopes={ + 46: Isotope(45.9526295, 0.0825, 46), + 47: Isotope(46.9517638, 0.0744, 47), + 48: Isotope(47.9479471, 0.7372, 48), + 49: Isotope(48.9478708, 0.0541, 49), + 50: Isotope(49.9447921, 0.0518, 50), + }, + ), + Element( + 23, + "V", + "Vanadium", + group=5, + period=4, + block="d", + series=8, + mass=50.9415, + eleneg=1.63, + eleaffin=0.525, + covrad=1.22, + atmrad=1.92, + vdwrad=0.0, + tboil=3650.0, + tmelt=2163.0, + density=6.09, + eleconfig="[Ar] 3d3 4s2", + oxistates="5*, 4, 3, 2, 0", + ionenergy=( + 6.7462, + 14.65, + 29.31, + 46.707, + 65.23, + 128.12, + 150.17, + 173.7, + 205.8, + 230.5, + 255.04, + 308.25, + 336.267, + 895.58, + 974.02, + ), + isotopes={ + 50: Isotope(49.9471628, 0.0025, 50), + 51: Isotope(50.9439637, 0.9975, 51), + }, + ), + Element( + 24, + "Cr", + "Chromium", + group=6, + period=4, + block="d", + series=8, + mass=51.9961, + eleneg=1.66, + eleaffin=0.67584, + covrad=1.18, + atmrad=1.85, + vdwrad=0.0, + tboil=2945.0, + tmelt=2130.0, + density=7.14, + eleconfig="[Ar] 3d5 4s", + oxistates="6, 3*, 2, 0", + ionenergy=( + 6.7665, + 16.5, + 30.96, + 49.1, + 69.3, + 90.56, + 161.1, + 184.7, + 209.3, + 244.4, + 270.8, + 298.0, + 355.0, + 384.3, + 1010.64, + ), + isotopes={ + 50: Isotope(49.9460496, 0.04345, 50), + 52: Isotope(51.9405119, 0.83789, 52), + 53: Isotope(52.9406538, 0.09501, 53), + 54: Isotope(53.9388849, 0.02365, 54), + }, + ), + Element( + 25, + "Mn", + "Manganese", + group=7, + period=4, + block="d", + series=8, + mass=54.938049, + eleneg=1.55, + eleaffin=0.0, + covrad=1.17, + atmrad=1.79, + vdwrad=0.0, + tboil=2235.0, + tmelt=1518.0, + density=7.44, + eleconfig="[Ar] 3d5 4s2", + oxistates="7, 6, 4, 3, 2*, 0, -1", + ionenergy=( + 7.434, + 15.64, + 33.667, + 51.2, + 72.4, + 95.0, + 119.27, + 196.46, + 221.8, + 248.3, + 286.0, + 314.4, + 343.6, + 404.0, + 435.3, + 1136.2, + ), + isotopes={55: Isotope(54.9380496, 1.0, 55)}, + ), + Element( + 26, + "Fe", + "Iron", + group=8, + period=4, + block="d", + series=8, + mass=55.845, + eleneg=1.83, + eleaffin=0.151, + covrad=1.17, + atmrad=1.72, + vdwrad=0.0, + tboil=3023.0, + tmelt=1808.0, + density=7.874, + eleconfig="[Ar] 3d6 4s2", + oxistates="6, 3*, 2, 0, -2", + ionenergy=( + 7.9024, + 16.18, + 30.651, + 54.8, + 75.0, + 99.0, + 125.0, + 151.06, + 235.04, + 262.1, + 290.4, + 330.8, + 361.0, + 392.2, + 457.0, + 485.5, + 1266.1, + ), + isotopes={ + 54: Isotope(53.9396148, 0.05845, 54), + 56: Isotope(55.9349421, 0.91754, 56), + 57: Isotope(56.9353987, 0.02119, 57), + 58: Isotope(57.9332805, 0.00282, 58), + }, + ), + Element( + 27, + "Co", + "Cobalt", + group=9, + period=4, + block="d", + series=8, + mass=58.9332, + eleneg=1.88, + eleaffin=0.6633, + covrad=1.16, + atmrad=1.67, + vdwrad=0.0, + tboil=3143.0, + tmelt=1768.0, + density=8.89, + eleconfig="[Ar] 3d7 4s2", + oxistates="3, 2*, 0, -1", + ionenergy=( + 7.881, + 17.06, + 33.5, + 51.3, + 79.5, + 102.0, + 129.0, + 157.0, + 186.13, + 276.0, + 305.0, + 336.0, + 376.0, + 411.0, + 444.0, + 512.0, + 546.8, + 1403.0, + ), + isotopes={59: Isotope(58.9332002, 1.0, 59)}, + ), + Element( + 28, + "Ni", + "Nickel", + group=10, + period=4, + block="d", + series=8, + mass=58.6934, + eleneg=1.91, + eleaffin=1.15716, + covrad=1.15, + atmrad=1.62, + vdwrad=1.63, + tboil=3005.0, + tmelt=1726.0, + density=8.91, + eleconfig="[Ar] 3d8 4s2", + oxistates="3, 2*, 0", + ionenergy=( + 7.6398, + 18.168, + 35.17, + 54.9, + 75.5, + 108.0, + 133.0, + 162.0, + 193.0, + 224.5, + 321.2, + 352.0, + 384.0, + 430.0, + 464.0, + 499.0, + 571.0, + 607.2, + 1547.0, + ), + isotopes={ + 58: Isotope(57.9353479, 0.680769, 58), + 60: Isotope(59.9307906, 0.262231, 60), + 61: Isotope(60.9310604, 0.011399, 61), + 62: Isotope(61.9283488, 0.036345, 62), + 64: Isotope(63.9279696, 0.009256, 64), + }, + ), + Element( + 29, + "Cu", + "Copper", + group=11, + period=4, + block="d", + series=8, + mass=63.546, + eleneg=1.9, + eleaffin=1.23578, + covrad=1.17, + atmrad=1.57, + vdwrad=1.4, + tboil=2840.0, + tmelt=1356.6, + density=8.92, + eleconfig="[Ar] 3d10 4s", + oxistates="2*, 1", + ionenergy=( + 7.7264, + 20.292, + 26.83, + 55.2, + 79.9, + 103.0, + 139.0, + 166.0, + 199.0, + 232.0, + 266.0, + 368.8, + 401.0, + 435.0, + 484.0, + 520.0, + 557.0, + 633.0, + 671.0, + 1698.0, + ), + isotopes={ + 63: Isotope(62.9296011, 0.6917, 63), + 65: Isotope(64.9277937, 0.3083, 65), + }, + ), + Element( + 30, + "Zn", + "Zinc", + group=12, + period=4, + block="d", + series=8, + mass=65.409, + eleneg=1.65, + eleaffin=0.0, + covrad=1.25, + atmrad=1.53, + vdwrad=1.39, + tboil=1180.0, + tmelt=692.73, + density=7.14, + eleconfig="[Ar] 3d10 4s2", + oxistates="2*", + ionenergy=( + 9.3942, + 17.964, + 39.722, + 59.4, + 82.6, + 108.0, + 134.0, + 174.0, + 203.0, + 238.0, + 274.0, + 310.8, + 419.7, + 454.0, + 490.0, + 542.0, + 579.0, + 619.0, + 698.8, + 738.0, + 1856.0, + ), + isotopes={ + 64: Isotope(63.9291466, 0.4863, 64), + 66: Isotope(65.9260368, 0.279, 66), + 67: Isotope(66.9271309, 0.041, 67), + 68: Isotope(67.9248476, 0.1875, 68), + 70: Isotope(69.925325, 0.0062, 70), + }, + ), + Element( + 31, + "Ga", + "Gallium", + group=13, + period=4, + block="p", + series=7, + mass=69.723, + eleneg=1.81, + eleaffin=0.41, + covrad=1.26, + atmrad=1.81, + vdwrad=1.87, + tboil=2478.0, + tmelt=302.92, + density=5.91, + eleconfig="[Ar] 3d10 4s2 4p", + oxistates="3*", + ionenergy=( + 5.9993, + 20.51, + 30.71, + 64.0, + ), + isotopes={ + 69: Isotope(68.925581, 0.60108, 69), + 71: Isotope(70.924705, 0.39892, 71), + }, + ), + Element( + 32, + "Ge", + "Germanium", + group=14, + period=4, + block="p", + series=5, + mass=72.64, + eleneg=2.01, + eleaffin=1.232712, + covrad=1.22, + atmrad=1.52, + vdwrad=0.0, + tboil=3107.0, + tmelt=1211.5, + density=5.32, + eleconfig="[Ar] 3d10 4s2 4p2", + oxistates="4*", + ionenergy=( + 7.8994, + 15.934, + 34.22, + 45.71, + 93.5, + ), + isotopes={ + 70: Isotope(69.9242504, 0.2084, 70), + 72: Isotope(71.9220762, 0.2754, 72), + 73: Isotope(72.9234594, 0.0773, 73), + 74: Isotope(73.9211782, 0.3628, 74), + 76: Isotope(75.9214027, 0.0761, 76, 1.58, "Zy"), + }, + ), # Zetta year + Element( + 33, + "As", + "Arsenic", + group=15, + period=4, + block="p", + series=5, + mass=74.9216, + eleneg=2.18, + eleaffin=0.814, + covrad=1.2, + atmrad=1.33, + vdwrad=1.85, + tboil=876.0, + tmelt=1090.0, + density=5.72, + eleconfig="[Ar] 3d10 4s2 4p3", + oxistates="5, 3*, -3", + ionenergy=( + 9.7886, + 18.633, + 28.351, + 50.13, + 62.63, + 127.6, + ), + isotopes={75: Isotope(74.9215964, 1.0, 75)}, + ), + Element( + 34, + "Se", + "Selenium", + group=16, + period=4, + block="p", + series=5, + mass=78.96, + eleneg=2.55, + eleaffin=2.02067, + covrad=1.16, + atmrad=1.22, + vdwrad=1.9, + tboil=958.0, + tmelt=494.0, + density=4.82, + eleconfig="[Ar] 3d10 4s2 4p4", + oxistates="6, 4*, -2", + ionenergy=( + 9.7524, + 21.9, + 30.82, + 42.944, + 68.3, + 81.7, + 155.4, + ), + isotopes={ + 74: Isotope(73.9224766, 0.0089, 74), + 75: Isotope(74.9225234, 0.0000, 75), + 76: Isotope(75.9192141, 0.0937, 76), + 77: Isotope(76.9199146, 0.0763, 77), + 78: Isotope(77.9173095, 0.2377, 78), + 79: Isotope(78.9184991, 0.0000, 79, 295, "ky"), + 80: Isotope(79.9165218, 0.4961, 80), + 82: Isotope(81.9167, 0.0873, 82, 97, "Ey"), + }, + ), # Exa year + Element( + 35, + "Br", + "Bromine", + group=17, + period=4, + block="p", + series=6, + mass=79.904, + eleneg=2.96, + eleaffin=3.363588, + covrad=1.14, + atmrad=1.12, + vdwrad=1.85, + tboil=331.85, + tmelt=265.95, + density=3.14, + eleconfig="[Ar] 3d10 4s2 4p5", + oxistates="7, 5, 3, 1, -1*", + ionenergy=( + 11.8138, + 21.8, + 36.0, + 47.3, + 59.7, + 88.6, + 103.0, + 192.8, + ), + isotopes={ + 79: Isotope(78.9183376, 0.5069, 79), + 81: Isotope(80.916291, 0.4931, 81), + }, + ), + Element( + 36, + "Kr", + "Krypton", + group=18, + period=4, + block="p", + series=2, + mass=83.798, + eleneg=0.0, + eleaffin=0.0, + covrad=1.12, + atmrad=1.03, + vdwrad=2.02, + tboil=120.85, + tmelt=116.0, + density=4.48, + eleconfig="[Ar] 3d10 4s2 4p6", + oxistates="2*", + ionenergy=( + 13.9996, + 24.359, + 36.95, + 52.5, + 64.7, + 78.5, + 110.0, + 126.0, + 230.39, + ), + isotopes={ + 78: Isotope(77.920386, 0.0035, 78), + 80: Isotope(79.916378, 0.0228, 80), + 81: Isotope(80.9165920, 0.0000, 81), + 82: Isotope(81.9134846, 0.1158, 82), + 83: Isotope(82.914136, 0.1149, 83), + 84: Isotope(83.911507, 0.57, 84), + 85: Isotope(84.9125273, 0.00, 85, 10.776, "y"), + 86: Isotope(85.9106103, 0.173, 86), + }, + ), + Element( + 37, + "Rb", + "Rubidium", + group=1, + period=5, + block="s", + series=3, + mass=85.4678, + eleneg=0.82, + eleaffin=0.485916, + covrad=2.16, + atmrad=2.98, + vdwrad=0.0, + tboil=961.0, + tmelt=312.63, + density=1.53, + eleconfig="[Kr] 5s", + oxistates="1*", + ionenergy=( + 4.1771, + 27.28, + 40.0, + 52.6, + 71.0, + 84.4, + 99.2, + 136.0, + 150.0, + 277.1, + ), + isotopes={ + 85: Isotope(84.9117893, 0.7217, 85), + 87: Isotope(86.9091835, 0.2783, 87, 42.23, "Gy"), + }, + ), + Element( + 38, + "Sr", + "Strontium", + group=2, + period=5, + block="s", + series=4, + mass=87.62, + eleneg=0.95, + eleaffin=0.05206, + covrad=1.91, + atmrad=2.45, + vdwrad=0.0, + tboil=1655.0, + tmelt=1042.0, + density=2.63, + eleconfig="[Kr] 5s2", + oxistates="2*", + ionenergy=( + 5.6949, + 11.03, + 43.6, + 57.0, + 71.6, + 90.8, + 106.0, + 122.3, + 162.0, + 177.0, + 324.1, + ), + isotopes={ + 84: Isotope(83.913425, 0.0056, 84), + 86: Isotope(85.9092624, 0.0986, 86), + 87: Isotope(86.9088793, 0.07, 87), + 88: Isotope(87.9056143, 0.8258, 88), + 89: Isotope(88.9074507, 0.0000, 89, 50.53, "d"), # day + 90: Isotope(89.9077379, 0.0000, 90, 28.79, "y"), + }, + ), + Element( + 39, + "Y", + "Yttrium", + group=3, + period=5, + block="d", + series=8, + mass=88.90585, + eleneg=1.22, + eleaffin=0.307, + covrad=1.62, + atmrad=2.27, + vdwrad=0.0, + tboil=3611.0, + tmelt=1795.0, + density=4.47, + eleconfig="[Kr] 4d 5s2", + oxistates="3*", + ionenergy=( + 6.2173, + 12.24, + 20.52, + 61.8, + 77.0, + 93.0, + 116.0, + 129.0, + 146.52, + 191.0, + 206.0, + 374.0, + ), + isotopes={ + 88: Isotope(87.9095011, 0.0, 88), + 89: Isotope(88.9058479, 1.0, 89), + 90: Isotope(89.9071519, 0.0, 90, 64.0, "h"), # hour + 91: Isotope(90.9073048, 0.0, 91, 58.51, "d"), + }, + ), + Element( + 40, + "Zr", + "Zirconium", + group=4, + period=5, + block="d", + series=8, + mass=91.224, + eleneg=1.33, + eleaffin=0.426, + covrad=1.45, + atmrad=2.16, + vdwrad=0.0, + tboil=4682.0, + tmelt=2128.0, + density=6.51, + eleconfig="[Kr] 4d2 5s2", + oxistates="4*", + ionenergy=( + 6.6339, + 13.13, + 22.99, + 34.34, + 81.5, + ), + isotopes={ + 90: Isotope(89.9047037, 0.5145, 90), + 91: Isotope(90.905645, 0.1122, 91), + 92: Isotope(91.9050401, 0.1715, 92), + 93: Isotope(92.9064760, 0.0000, 93, 1.53, "My"), # Mega year + 94: Isotope(93.9063158, 0.1738, 94), + 95: Isotope(94.9080426, 0.1738, 95, 64.032, "d"), + 96: Isotope(95.908276, 0.028, 96, 24.0, "Ey"), + }, + ), # Exa year + Element( + 41, + "Nb", + "Niobium", + group=5, + period=5, + block="d", + series=8, + mass=92.90638, + eleneg=1.6, + eleaffin=0.893, + covrad=1.34, + atmrad=2.08, + vdwrad=0.0, + tboil=5015.0, + tmelt=2742.0, + density=8.58, + eleconfig="[Kr] 4d4 5s", + oxistates="5*, 3", + ionenergy=( + 6.7589, + 14.32, + 25.04, + 38.3, + 50.55, + 102.6, + 125.0, + ), + isotopes={ + 93: Isotope(92.9063775, 1.0, 93), + 94: Isotope(93.9072839, 0.0, 94), + 95: Isotope(94.9068358, 0.0, 95, 34.991, "s"), + }, + ), + Element( + 42, + "Mo", + "Molybdenum", + group=6, + period=5, + block="d", + series=8, + mass=95.94, + eleneg=2.16, + eleaffin=0.7472, + covrad=1.3, + atmrad=2.01, + vdwrad=0.0, + tboil=4912.0, + tmelt=2896.0, + density=10.28, + eleconfig="[Kr] 4d5 5s", + oxistates="6*, 5, 4, 3, 2, 0", + ionenergy=( + 7.0924, + 16.15, + 27.16, + 46.4, + 61.2, + 68.0, + 126.8, + 153.0, + ), + isotopes={ + 92: Isotope(91.90681, 0.1484, 92), + 94: Isotope(93.9050876, 0.0925, 94), + 95: Isotope(94.9058415, 0.1592, 95), + 96: Isotope(95.9046789, 0.1668, 96), + 97: Isotope(96.906021, 0.0955, 97), + 98: Isotope(97.9054078, 0.2413, 98), + 99: Isotope(98.9077119, 0.0000, 99, 65.94, "h"), + 100: Isotope(99.907477, 0.0963, 100, 8.5, "Ey"), + }, + ), + Element( + 43, + "Tc", + "Technetium", + group=7, + period=5, + block="d", + series=8, + mass=97.907216, + eleneg=1.9, + eleaffin=0.55, + covrad=1.27, + atmrad=1.95, + vdwrad=0.0, + tboil=4538.0, + tmelt=2477.0, + density=11.49, + eleconfig="[Kr] 4d5 5s2", + oxistates="7*", + ionenergy=( + 7.28, + 15.26, + 29.54, + ), + isotopes={ + 98: Isotope(97.907216, 1.0, 98, 4.2, "My"), + 99: Isotope(98.9062547, 0.0, 99, 211.1, "ky"), + }, + ), + Element( + 44, + "Ru", + "Ruthenium", + group=8, + period=5, + block="d", + series=8, + mass=101.07, + eleneg=2.2, + eleaffin=1.04638, + covrad=1.25, + atmrad=1.89, + vdwrad=0.0, + tboil=4425.0, + tmelt=2610.0, + density=12.45, + eleconfig="[Kr] 4d7 5s", + oxistates="8, 6, 4*, 3*, 2, 0, -2", + ionenergy=( + 7.3605, + 16.76, + 28.47, + ), + isotopes={ + 96: Isotope(95.907598, 0.0554, 96), + 98: Isotope(97.905287, 0.0187, 98), + 99: Isotope(98.9059393, 0.1276, 99), + 100: Isotope(99.9042197, 0.126, 100), + 101: Isotope(100.9055822, 0.1706, 101), + 102: Isotope(101.9043495, 0.3155, 102), + 103: Isotope(102.9063238, 0.0000, 103, 39.26, "s"), + 104: Isotope(103.90543, 0.1862, 104), + 106: Isotope(105.9073294, 0.0000, 106, 373.59, "d"), + }, + ), + Element( + 45, + "Rh", + "Rhodium", + group=9, + period=5, + block="d", + series=8, + mass=102.9055, + eleneg=2.28, + eleaffin=1.14289, + covrad=1.25, + atmrad=1.83, + vdwrad=0.0, + tboil=3970.0, + tmelt=2236.0, + density=12.41, + eleconfig="[Kr] 4d8 5s", + oxistates="5, 4, 3*, 1*, 2, 0", + ionenergy=( + 7.4589, + 18.08, + 31.06, + ), + isotopes={ + 102: Isotope(101.9068432, 0.0, 102, 207.0, "d"), + 103: Isotope(102.905504, 1.0, 103), + 106: Isotope(105.9072871, 0.0, 106, 29.8, "s"), + }, + ), # second + Element( + 46, + "Pd", + "Palladium", + group=10, + period=5, + block="d", + series=8, + mass=106.42, + eleneg=2.2, + eleaffin=0.56214, + covrad=1.28, + atmrad=1.79, + vdwrad=1.63, + tboil=3240.0, + tmelt=1825.0, + density=12.02, + eleconfig="[Kr] 4d10", + oxistates="4, 2*, 0", + ionenergy=( + 8.3369, + 19.43, + 32.93, + ), + isotopes={ + 102: Isotope(101.905608, 0.0102, 102), + 104: Isotope(103.904035, 0.1114, 104), + 105: Isotope(104.905084, 0.2233, 105), + 106: Isotope(105.903483, 0.2733, 106), + 107: Isotope(106.9051335, 0.0000, 107, 6.5, "My"), + 108: Isotope(107.903894, 0.2646, 108), + 110: Isotope(109.905152, 0.1172, 110), + }, + ), + Element( + 47, + "Ag", + "Silver", + group=11, + period=5, + block="d", + series=8, + mass=107.8682, + eleneg=1.93, + eleaffin=1.30447, + covrad=1.34, + atmrad=1.75, + vdwrad=1.72, + tboil=2436.0, + tmelt=1235.1, + density=10.49, + eleconfig="[Kr] 4d10 5s", + oxistates="2, 1*", + ionenergy=( + 7.5762, + 21.49, + 34.83, + ), + isotopes={ + 107: Isotope(106.905093, 0.51839, 107), + 109: Isotope(108.904756, 0.48161, 109), + 110: Isotope(109.9061072, 0.00000, 110, 24.6, "s"), + }, + ), + Element( + 48, + "Cd", + "Cadmium", + group=12, + period=5, + block="d", + series=8, + mass=112.411, + eleneg=1.69, + eleaffin=0.0, + covrad=1.48, + atmrad=1.71, + vdwrad=1.58, + tboil=1040.0, + tmelt=594.26, + density=8.64, + eleconfig="[Kr] 4d10 5s2", + oxistates="2*", + ionenergy=( + 8.9938, + 16.908, + 37.48, + ), + isotopes={ + 106: Isotope(105.906458, 0.0125, 106), + 108: Isotope(107.904183, 0.0089, 108), + 109: Isotope(108.9049823, 0.00, 109), + 110: Isotope(109.903006, 0.1249, 110), + 111: Isotope(110.904182, 0.128, 111), + 112: Isotope(111.9027572, 0.2413, 112), + # Peta year + 113: Isotope(112.9044009, 0.1222, 113, 7.7, "Py"), + 114: Isotope(113.9033581, 0.2873, 114), + 115: Isotope(114.9054310, 0.0000, 115, 53.46, "h"), + 116: Isotope(115.904755, 0.0749, 116, 30.0, "Ey"), + }, + ), # Exa year + Element( + 49, + "In", + "Indium", + group=13, + period=5, + block="p", + series=7, + mass=114.818, + eleneg=1.78, + eleaffin=0.404, + covrad=1.44, + atmrad=2.0, + vdwrad=1.93, + tboil=2350.0, + tmelt=429.78, + density=7.31, + eleconfig="[Kr] 4d10 5s2 5p", + oxistates="3*", + ionenergy=( + 5.7864, + 18.869, + 28.03, + 28.03, + ), + isotopes={ + 113: Isotope(112.904061, 0.0429, 113), + 115: Isotope(114.903878, 0.9571, 115, 441.0, "Ty"), + }, + ), # Tera year + Element( + 50, + "Sn", + "Tin", + group=14, + period=5, + block="p", + series=7, + mass=118.71, + eleneg=1.96, + eleaffin=1.112066, + covrad=1.41, + atmrad=1.72, + vdwrad=2.17, + tboil=2876.0, + tmelt=505.12, + density=7.29, + eleconfig="[Kr] 4d10 5s2 5p2", + oxistates="4*, 2*", + ionenergy=( + 7.3439, + 14.632, + 30.502, + 40.734, + 72.28, + ), + isotopes={ + 112: Isotope(111.904821, 0.0097, 112), + 114: Isotope(113.902782, 0.0066, 114), + 115: Isotope(114.903346, 0.0034, 115), + 116: Isotope(115.901744, 0.1454, 116), + 117: Isotope(116.902954, 0.0768, 117), + 118: Isotope(117.901606, 0.2422, 118), + 119: Isotope(118.903309, 0.0859, 119), + 120: Isotope(119.9021966, 0.3258, 120), + 121: Isotope(120.9042355, 0.0000, 121, 27.03, "h"), + 122: Isotope(121.9034401, 0.0463, 122), + 123: Isotope(122.9057208, 0.0463, 123, 129.2, "d"), + 124: Isotope(123.9052746, 0.0579, 124), + 125: Isotope(124.9077841, 0.0000, 125, 9.64, "d"), + 126: Isotope(125.907653, 0.0000, 126, 230.0, "ky"), + }, + ), # kilo year + Element( + 51, + "Sb", + "Antimony", + group=15, + period=5, + block="p", + series=5, + mass=121.76, + eleneg=2.05, + eleaffin=1.047401, + covrad=1.4, + atmrad=1.53, + vdwrad=0.0, + tboil=1860.0, + tmelt=903.91, + density=6.69, + eleconfig="[Kr] 4d10 5s2 5p3", + oxistates="5, 3*, -3", + ionenergy=( + 8.6084, + 16.53, + 25.3, + 44.2, + 56.0, + 108.0, + ), + isotopes={ + 121: Isotope(120.903818, 0.5721, 121), + 122: Isotope(121.9051737, 0.0000, 122, 2.7238, "d"), + 123: Isotope(122.9042157, 0.4279, 123), + 124: Isotope(123.9059357, 0.0000, 124, 60.2, "d"), + 125: Isotope(124.9052538, 0.0000, 125, 2.75856, "y"), + 126: Isotope(125.907247, 0.0000, 126, 12.35, "d"), + }, + ), + Element( + 52, + "Te", + "Tellurium", + group=16, + period=5, + block="p", + series=5, + mass=127.6, + eleneg=2.1, + eleaffin=1.970875, + covrad=1.36, + atmrad=1.42, + vdwrad=2.06, + tboil=1261.0, + tmelt=722.72, + density=6.25, + eleconfig="[Kr] 4d10 5s2 5p4", + oxistates="6, 4*, -2", + ionenergy=( + 9.0096, + 18.6, + 27.96, + 37.41, + 58.75, + 70.7, + 137.0, + ), + isotopes={ + 120: Isotope(119.90402, 0.0009, 120), + 121: Isotope(120.904936, 0.0000, 121, 19.16, "d"), + 122: Isotope(121.9030471, 0.0255, 122), + # >600 Tera year + 123: Isotope(122.904273, 0.0089, 123, 600.0, "Ty"), + 124: Isotope(123.9028195, 0.0474, 124), + 125: Isotope(124.9044247, 0.0707, 125), + 126: Isotope(125.9033055, 0.1884, 126), + 127: Isotope(126.9052263, 0.0000, 127, 9.35, "h"), + # Yotta year + 128: Isotope(127.9044614, 0.3174, 128, 2.2, "Yy"), + 129: Isotope(128.9062244, 0.0000, 129, 69.6, "m"), # minute + 130: Isotope(129.9062228, 0.3408, 130, 790.0, "Ey"), + }, + ), # Exa year + Element( + 53, + "I", + "Iodine", + group=17, + period=5, + block="p", + series=6, + mass=126.90447, + eleneg=2.66, + eleaffin=3.059038, + covrad=1.33, + atmrad=1.32, + vdwrad=1.98, + tboil=457.5, + tmelt=386.7, + density=4.94, + eleconfig="[Kr] 4d10 5s2 5p5", + oxistates="7, 5, 1, -1*", + ionenergy=( + 10.4513, + 19.131, + 33.0, + ), + isotopes={ + 127: Isotope(126.904468, 1.0, 127), + 129: Isotope(128.9049877, 0.0, 129, 15.7, "My"), + }, + ), # Mega year + Element( + 54, + "Xe", + "Xenon", + group=18, + period=5, + block="p", + series=2, + mass=131.293, + eleneg=0.0, + eleaffin=0.0, + covrad=1.31, + atmrad=1.24, + vdwrad=2.16, + tboil=165.1, + tmelt=161.39, + density=4.49, + eleconfig="[Kr] 4d10 5s2 5p6", + oxistates="2, 4, 6", + ionenergy=( + 12.1298, + 21.21, + 32.1, + ), + isotopes={ + 124: Isotope(123.9058958, 0.0009, 124), + 125: Isotope(124.9063955, 0.000, 125, 16.9, "h"), + 126: Isotope(125.904269, 0.0009, 126), + 128: Isotope(127.9035304, 0.0192, 128), + 129: Isotope(128.9047795, 0.2644, 129), + 130: Isotope(129.9035079, 0.0408, 130), + 131: Isotope(130.9050819, 0.2118, 131), + 132: Isotope(131.9041545, 0.2689, 132), + 133: Isotope(132.9059107, 0.0000, 133, 5.2475, "d"), + 134: Isotope(133.9053945, 0.1044, 134), + 135: Isotope(134.9072275, 0.0000, 135, 9.14, "h"), + 136: Isotope(135.90722, 0.0887, 136), + }, + ), + Element( + 55, + "Cs", + "Caesium", + group=1, + period=6, + block="s", + series=3, + mass=132.90545, + eleneg=0.79, + eleaffin=0.471626, + covrad=2.35, + atmrad=3.34, + vdwrad=0.0, + tboil=944.0, + tmelt=301.54, + density=1.9, + eleconfig="[Xe] 6s", + oxistates="1*", + ionenergy=( + 3.8939, + 25.1, + ), + isotopes={ + 133: Isotope(132.905447, 1.0, 133), + 134: Isotope(133.906718475, 0.0, 134, 2.0648, "y"), + # Mega year + 135: Isotope(134.9059770, 0.0, 135, 2.3, "My"), + 136: Isotope(135.9073116, 0.0, 136, 13.16, "d"), + 137: Isotope(136.90708947, 0.0, 137, 30.1671, "y"), + }, + ), + Element( + 56, + "Ba", + "Barium", + group=2, + period=6, + block="s", + series=4, + mass=137.327, + eleneg=0.89, + eleaffin=0.14462, + covrad=1.98, + atmrad=2.78, + vdwrad=0.0, + tboil=2078.0, + tmelt=1002.0, + density=3.65, + eleconfig="[Xe] 6s2", + oxistates="2*", + ionenergy=( + 5.2117, + 100.004, + ), + isotopes={ + 130: Isotope(129.90631, 0.00106, 130), + 132: Isotope(131.905056, 0.00101, 132), + 133: Isotope(132.9060075, 0.0, 133, 10.51, "y"), + 134: Isotope(133.904503, 0.02417, 134), + 135: Isotope(134.905683, 0.06592, 135), + 136: Isotope(135.90457, 0.07854, 136), + 137: Isotope(136.905821, 0.11232, 137), + 138: Isotope(137.905241, 0.71698, 138), + }, + ), + Element( + 57, + "La", + "Lanthanum", + group=3, + period=6, + block="f", + series=9, + mass=138.9055, + eleneg=1.1, + eleaffin=0.47, + covrad=1.69, + atmrad=2.74, + vdwrad=0.0, + tboil=3737.0, + tmelt=1191.0, + density=6.16, + eleconfig="[Xe] 5d 6s2", + oxistates="3*", + ionenergy=( + 5.5769, + 11.06, + 19.175, + ), + isotopes={ + 137: Isotope(136.906494, 0.0, 137), + # Giga year + 138: Isotope(137.9071119, 0.090, 138, 102.0, "Gy"), + 139: Isotope(138.906348, 0.9991, 139), + }, + ), + Element( + 58, + "Ce", + "Cerium", + group=3, + period=6, + block="f", + series=9, + mass=140.116, + eleneg=1.12, + eleaffin=0.5, + covrad=1.65, + atmrad=2.7, + vdwrad=0.0, + tboil=3715.0, + tmelt=1071.0, + density=6.77, + eleconfig="[Xe] 4f 5d 6s2", + oxistates="4, 3*", + ionenergy=( + 5.5387, + 10.85, + 20.2, + 36.72, + ), + isotopes={ + 136: Isotope(135.90714, 0.00185, 136), + 138: Isotope(137.905986, 0.00251, 138), + 139: Isotope(138.9066527, 0.00000, 139, 137.641, "d"), + 140: Isotope(139.905434, 0.8845, 140), + 141: Isotope(140.9082763, 0.0000, 141, 32.508, "d"), + 142: Isotope(141.90924, 0.11114, 142), + 144: Isotope(143.9136473, 0.00000, 144, 284.91, "d"), + }, + ), + Element( + 59, + "Pr", + "Praseodymium", + group=3, + period=6, + block="f", + series=9, + mass=140.90765, + eleneg=1.13, + eleaffin=0.5, + covrad=1.65, + atmrad=2.67, + vdwrad=0.0, + tboil=3785.0, + tmelt=1204.0, + density=6.48, + eleconfig="[Xe] 4f3 6s2", + oxistates="4, 3*", + ionenergy=( + 5.473, + 10.55, + 21.62, + 38.95, + 57.45, + ), + isotopes={ + 141: Isotope(140.907648, 1.0, 141), + 144: Isotope(143.9133052, 0.0, 144, 17.28, "m"), + }, + ), # minute + Element( + 60, + "Nd", + "Neodymium", + group=3, + period=6, + block="f", + series=9, + mass=144.24, + eleneg=1.14, + eleaffin=0.5, + covrad=1.64, + atmrad=2.64, + vdwrad=0.0, + tboil=3347.0, + tmelt=1294.0, + density=7.0, + eleconfig="[Xe] 4f4 6s2", + oxistates="3*", + ionenergy=( + 5.525, + 10.72, + ), + isotopes={ + 142: Isotope(141.907719, 0.272, 142), + 143: Isotope(142.90981, 0.122, 143), + # Peta year + 144: Isotope(143.910083, 0.238, 144, 2.29, "Py"), + 145: Isotope(144.912569, 0.083, 145), + 146: Isotope(145.913112, 0.172, 146), + 147: Isotope(146.9161004, 0.000, 147, 10.98, "d"), # day + 148: Isotope(147.916889, 0.057, 148), + 150: Isotope(149.9208909, 0.056, 150, 6.7, "Ey"), + }, + ), # Exa year + Element( + 61, + "Pm", + "Promethium", + group=3, + period=6, + block="f", + series=9, + mass=144.912744, + eleneg=1.13, + eleaffin=0.5, + covrad=1.63, + atmrad=2.62, + vdwrad=0.0, + tboil=3273.0, + tmelt=1315.0, + density=7.22, + eleconfig="[Xe] 4f5 6s2", + oxistates="3*", + ionenergy=( + 5.582, + 10.9, + ), + isotopes={ + 145: Isotope(144.9127490, 0.0, 145, 17.7, "y"), + 146: Isotope(145.9146963, 0.0, 146, 5.53, "y"), + 147: Isotope(146.9151385, 0.0, 147, 2.6234, "y"), + 148: Isotope(147.9174746, 0.0, 148, 5.368, "d"), + }, + ), + Element( + 62, + "Sm", + "Samarium", + group=3, + period=6, + block="f", + series=9, + mass=150.36, + eleneg=1.17, + eleaffin=0.5, + covrad=1.62, + atmrad=2.59, + vdwrad=0.0, + tboil=2067.0, + tmelt=1347.0, + density=7.54, + eleconfig="[Xe] 4f6 6s2", + oxistates="3*, 2", + ionenergy=( + 5.6437, + 11.07, + ), + isotopes={ + 144: Isotope(143.911995, 0.0307, 144), + 146: Isotope(145.9130409, 0.0, 146, 103.0, "My"), + 147: Isotope(146.9148979, 0.1499, 147, 106.0, "Gy"), + 148: Isotope(147.914818, 0.1124, 148, 7.0, "Py"), + 149: Isotope(148.91718, 0.1382, 149), + 150: Isotope(149.917271, 0.0738, 150), + 151: Isotope(150.9199324, 0.0000, 151, 90.0, "y"), + 152: Isotope(151.919728, 0.2675, 152), + 154: Isotope(153.922205, 0.2275, 154), + }, + ), + Element( + 63, + "Eu", + "Europium", + group=3, + period=6, + block="f", + series=9, + mass=151.964, + eleneg=1.2, + eleaffin=0.5, + covrad=1.85, + atmrad=2.56, + vdwrad=0.0, + tboil=1800.0, + tmelt=1095.0, + density=5.25, + eleconfig="[Xe] 4f7 6s2", + oxistates="3*, 2", + ionenergy=( + 5.6704, + 11.25, + ), + isotopes={ + 151: Isotope(150.919846, 0.4781, 151), + 152: Isotope(151.9217445, 0.0000, 152, 13.537, "y"), + 153: Isotope(152.921226, 0.5219, 153), + 154: Isotope(153.9229792, 0.0000, 154, 8.593, "y"), + 155: Isotope(154.9228933, 0.0000, 155, 4.7611, "y"), + }, + ), + Element( + 64, + "Gd", + "Gadolinium", + group=3, + period=6, + block="f", + series=9, + mass=157.25, + eleneg=1.2, + eleaffin=0.5, + covrad=1.61, + atmrad=2.54, + vdwrad=0.0, + tboil=3545.0, + tmelt=1585.0, + density=7.89, + eleconfig="[Xe] 4f7 5d 6s2", + oxistates="3*", + ionenergy=( + 6.1498, + 12.1, + ), + isotopes={ + 152: Isotope(151.919788, 0.002, 152, 108.0, "Ty"), + 153: Isotope(152.9217495, 0.0, 153, 240.4, "d"), + 154: Isotope(153.920862, 0.0218, 154), + 155: Isotope(154.922619, 0.148, 155), + 156: Isotope(155.92212, 0.2047, 156), + 157: Isotope(156.923957, 0.1565, 157), + 158: Isotope(157.924101, 0.2484, 158), + 159: Isotope(158.9263887, 0.0000, 159, 18.479, "h"), + 160: Isotope(159.927051, 0.2186, 160), + }, + ), + Element( + 65, + "Tb", + "Terbium", + group=3, + period=6, + block="f", + series=9, + mass=158.92534, + eleneg=1.2, + eleaffin=0.5, + covrad=1.59, + atmrad=2.51, + vdwrad=0.0, + tboil=3500.0, + tmelt=1629.0, + density=8.25, + eleconfig="[Xe] 4f9 6s2", + oxistates="4, 3*", + ionenergy=( + 5.8638, + 11.52, + ), + isotopes={ + 158: Isotope(157.9254131, 0.0, 158, 180.0, "y"), + 159: Isotope(158.925343, 1.0, 159), + 160: Isotope(159.9271676, 0.0, 160, 72.3, "d"), + }, + ), + Element( + 66, + "Dy", + "Dysprosium", + group=3, + period=6, + block="f", + series=9, + mass=162.5, + eleneg=1.22, + eleaffin=0.5, + covrad=1.59, + atmrad=2.49, + vdwrad=0.0, + tboil=2840.0, + tmelt=1685.0, + density=8.56, + eleconfig="[Xe] 4f10 6s2", + oxistates="3*", + ionenergy=( + 5.9389, + 11.67, + ), + isotopes={ + 156: Isotope(155.924278, 0.0006, 156), + 158: Isotope(157.924405, 0.001, 158), + 160: Isotope(159.925194, 0.0234, 160), + 161: Isotope(160.92693, 0.1891, 161), + 162: Isotope(161.926795, 0.2551, 162), + 163: Isotope(162.928728, 0.249, 163), + 164: Isotope(163.929171, 0.2818, 164), + }, + ), + Element( + 67, + "Ho", + "Holmium", + group=3, + period=6, + block="f", + series=9, + mass=164.93032, + eleneg=1.23, + eleaffin=0.5, + covrad=1.58, + atmrad=2.47, + vdwrad=0.0, + tboil=2968.0, + tmelt=1747.0, + density=8.78, + eleconfig="[Xe] 4f11 6s2", + oxistates="3*", + ionenergy=( + 6.0215, + 11.8, + ), + isotopes={ + 165: Isotope(164.930319, 1.0, 165), + 166: Isotope(165.9322842, 0.0, 166), + }, + ), + Element( + 68, + "Er", + "Erbium", + group=3, + period=6, + block="f", + series=9, + mass=167.259, + eleneg=1.24, + eleaffin=0.5, + covrad=1.57, + atmrad=2.45, + vdwrad=0.0, + tboil=3140.0, + tmelt=1802.0, + density=9.05, + eleconfig="[Xe] 4f12 6s2", + oxistates="3*", + ionenergy=( + 6.1077, + 11.93, + ), + isotopes={ + 162: Isotope(161.928775, 0.0014, 162), + 164: Isotope(163.929197, 0.0161, 164), + 166: Isotope(165.93029, 0.3361, 166), + 167: Isotope(166.932045, 0.2293, 167), + 168: Isotope(167.932368, 0.2678, 168), + 170: Isotope(169.93546, 0.1493, 170), + }, + ), + Element( + 69, + "Tm", + "Thulium", + group=3, + period=6, + block="f", + series=9, + mass=168.93421, + eleneg=1.25, + eleaffin=0.5, + covrad=1.56, + atmrad=2.42, + vdwrad=0.0, + tboil=2223.0, + tmelt=1818.0, + density=9.32, + eleconfig="[Xe] 4f13 6s2", + oxistates="3*, 2", + ionenergy=( + 6.1843, + 12.05, + 23.71, + ), + isotopes={ + 169: Isotope(168.934211, 1.0, 169), + 170: Isotope(169.9358014, 0.0, 170), + 171: Isotope(170.9364294, 0.0, 171), + }, + ), + Element( + 70, + "Yb", + "Ytterbium", + group=3, + period=6, + block="f", + series=9, + mass=173.04, + eleneg=1.1, + eleaffin=0.5, + covrad=1.74, + atmrad=2.4, + vdwrad=0.0, + tboil=1469.0, + tmelt=1092.0, + density=9.32, + eleconfig="[Xe] 4f14 6s2", + oxistates="3*, 2", + ionenergy=( + 6.2542, + 12.17, + 25.2, + ), + isotopes={ + 168: Isotope(167.933894, 0.0013, 168), + 170: Isotope(169.934759, 0.0304, 170), + 171: Isotope(170.936322, 0.1428, 171), + 172: Isotope(171.9363777, 0.2183, 172), + 173: Isotope(172.9382068, 0.1613, 173), + 174: Isotope(173.9388581, 0.3183, 174), + 176: Isotope(175.942568, 0.1276, 176), + }, + ), + Element( + 71, + "Lu", + "Lutetium", + group=3, + period=6, + block="d", + series=9, + mass=174.967, + eleneg=1.27, + eleaffin=0.5, + covrad=1.56, + atmrad=2.25, + vdwrad=0.0, + tboil=3668.0, + tmelt=1936.0, + density=9.84, + eleconfig="[Xe] 4f14 5d 6s2", + oxistates="3*", + ionenergy=( + 5.4259, + 13.9, + ), + isotopes={ + 175: Isotope(174.9407679, 0.9741, 175), + 176: Isotope(175.9426824, 0.0259, 176), + }, + ), + Element( + 72, + "Hf", + "Hafnium", + group=4, + period=6, + block="d", + series=8, + mass=178.49, + eleneg=1.3, + eleaffin=0.0, + covrad=1.44, + atmrad=2.16, + vdwrad=0.0, + tboil=4875.0, + tmelt=2504.0, + density=13.31, + eleconfig="[Xe] 4f14 5d2 6s2", + oxistates="4*", + ionenergy=( + 6.8251, + 14.9, + 23.3, + 33.3, + ), + isotopes={ + 174: Isotope(173.94004, 0.0016, 174), + 176: Isotope(175.9414018, 0.0526, 176), + 177: Isotope(176.94322, 0.186, 177), + 178: Isotope(177.9436977, 0.2728, 178), + 179: Isotope(178.9458151, 0.1362, 179), + 180: Isotope(179.9465488, 0.3508, 180), + }, + ), + Element( + 73, + "Ta", + "Tantalum", + group=5, + period=6, + block="d", + series=8, + mass=180.9479, + eleneg=1.5, + eleaffin=0.322, + covrad=1.34, + atmrad=2.09, + vdwrad=0.0, + tboil=5730.0, + tmelt=3293.0, + density=16.68, + eleconfig="[Xe] 4f14 5d3 6s2", + oxistates="5*", + ionenergy=(7.5496,), + isotopes={ + 180: Isotope(179.947466, 0.00012, 180), + 181: Isotope(180.947996, 0.99988, 181), + }, + ), + Element( + 74, + "W", + "Tungsten", + group=6, + period=6, + block="d", + series=8, + mass=183.84, + eleneg=2.36, + eleaffin=0.815, + covrad=1.3, + atmrad=2.02, + vdwrad=0.0, + tboil=5825.0, + tmelt=3695.0, + density=19.26, + eleconfig="[Xe] 4f14 5d4 6s2", + oxistates="6*, 5, 4, 3, 2, 0", + ionenergy=(7.864,), + isotopes={ + 180: Isotope(179.946706, 0.0012, 180), + 182: Isotope(181.948206, 0.265, 182), + 183: Isotope(182.9502245, 0.1431, 183), + 184: Isotope(183.9509326, 0.3064, 184), + 186: Isotope(185.954362, 0.2843, 186), + }, + ), + Element( + 75, + "Re", + "Rhenium", + group=7, + period=6, + block="d", + series=8, + mass=186.207, + eleneg=1.9, + eleaffin=0.15, + covrad=1.28, + atmrad=1.97, + vdwrad=0.0, + tboil=5870.0, + tmelt=3455.0, + density=21.03, + eleconfig="[Xe] 4f14 5d5 6s2", + oxistates="7, 6, 4, 2, -1", + ionenergy=(7.8335,), + isotopes={ + 185: Isotope(184.9529557, 0.374, 185), + 187: Isotope(186.9557508, 0.626, 187), + }, + ), + Element( + 76, + "Os", + "Osmium", + group=8, + period=6, + block="d", + series=8, + mass=190.23, + eleneg=2.2, + eleaffin=1.0778, + covrad=1.26, + atmrad=1.92, + vdwrad=0.0, + tboil=5300.0, + tmelt=3300.0, + density=22.61, + eleconfig="[Xe] 4f14 5d6 6s2", + oxistates="8, 6, 4*, 3, 2, 0, -2", + ionenergy=(8.4382,), + isotopes={ + 184: Isotope(183.952491, 0.0002, 184), + 186: Isotope(185.953838, 0.0159, 186), + 187: Isotope(186.9557479, 0.0196, 187), + 188: Isotope(187.955836, 0.1324, 188), + 189: Isotope(188.9581449, 0.1615, 189), + 190: Isotope(189.958445, 0.2626, 190), + 192: Isotope(191.961479, 0.4078, 192), + }, + ), + Element( + 77, + "Ir", + "Iridium", + group=9, + period=6, + block="d", + series=8, + mass=192.217, + eleneg=2.2, + eleaffin=1.56436, + covrad=1.27, + atmrad=1.87, + vdwrad=0.0, + tboil=4700.0, + tmelt=2720.0, + density=22.65, + eleconfig="[Xe] 4f14 5d7 6s2", + oxistates="6, 4*, 3, 2, 1*, 0, -1", + ionenergy=(8.967,), + isotopes={ + 191: Isotope(190.960591, 0.373, 191), + 193: Isotope(192.962924, 0.627, 193), + }, + ), + Element( + 78, + "Pt", + "Platinum", + group=10, + period=6, + block="d", + series=8, + mass=195.078, + eleneg=2.28, + eleaffin=2.1251, + covrad=1.3, + atmrad=1.83, + vdwrad=1.75, + tboil=4100.0, + tmelt=2042.1, + density=21.45, + eleconfig="[Xe] 4f14 5d9 6s", + oxistates="4*, 2*, 0", + ionenergy=( + 8.9588, + 18.563, + ), + isotopes={ + 190: Isotope(189.95993, 0.00014, 190), + 192: Isotope(191.961035, 0.00782, 192), + 194: Isotope(193.962664, 0.32967, 194), + 195: Isotope(194.964774, 0.33832, 195), + 196: Isotope(195.964935, 0.25242, 196), + 198: Isotope(197.967876, 0.07163, 198), + }, + ), + Element( + 79, + "Au", + "Gold", + group=11, + period=6, + block="d", + series=8, + mass=196.96655, + eleneg=2.54, + eleaffin=2.30861, + covrad=1.34, + atmrad=1.79, + vdwrad=1.66, + tboil=3130.0, + tmelt=1337.58, + density=19.32, + eleconfig="[Xe] 4f14 5d10 6s", + oxistates="3*, 1", + ionenergy=( + 9.2255, + 20.5, + ), + isotopes={197: Isotope(196.966552, 1.0, 197)}, + ), + Element( + 80, + "Hg", + "Mercury", + group=12, + period=6, + block="d", + series=8, + mass=200.59, + eleneg=2.0, + eleaffin=0.0, + covrad=1.49, + atmrad=1.76, + vdwrad=0.0, + tboil=629.88, + tmelt=234.31, + density=13.55, + eleconfig="[Xe] 4f14 5d10 6s2", + oxistates="2*, 1", + ionenergy=( + 10.4375, + 18.756, + 34.2, + ), + isotopes={ + 196: Isotope(195.965815, 0.0015, 196), + 198: Isotope(197.966752, 0.0997, 198), + 199: Isotope(198.968262, 0.1687, 199), + 200: Isotope(199.968309, 0.231, 200), + 201: Isotope(200.970285, 0.1318, 201), + 202: Isotope(201.970626, 0.2986, 202), + 204: Isotope(203.973476, 0.0687, 204), + }, + ), + Element( + 81, + "Tl", + "Thallium", + group=13, + period=6, + block="p", + series=7, + mass=204.3833, + eleneg=2.04, + eleaffin=0.377, + covrad=1.48, + atmrad=2.08, + vdwrad=1.96, + tboil=1746.0, + tmelt=577.0, + density=11.85, + eleconfig="[Xe] 4f14 5d10 6s2 6p", + oxistates="3, 1*", + ionenergy=( + 6.1082, + 20.428, + 29.83, + ), + isotopes={ + 203: Isotope(202.972329, 0.29524, 203), + 205: Isotope(204.974412, 0.70476, 205), + }, + ), + Element( + 82, + "Pb", + "Lead", + group=14, + period=6, + block="p", + series=7, + mass=207.2, + eleneg=2.33, + eleaffin=0.364, + covrad=1.47, + atmrad=1.81, + vdwrad=2.02, + tboil=2023.0, + tmelt=600.65, + density=11.34, + eleconfig="[Xe] 4f14 5d10 6s2 6p2", + oxistates="4, 2*", + ionenergy=( + 7.4167, + 15.032, + 31.937, + 42.32, + 68.8, + ), + isotopes={ + 204: Isotope(203.973029, 0.014, 204), + 206: Isotope(205.974449, 0.241, 206), + 207: Isotope(206.975881, 0.221, 207), + 208: Isotope(207.976636, 0.524, 208), + }, + ), + Element( + 83, + "Bi", + "Bismuth", + group=15, + period=6, + block="p", + series=7, + mass=208.98038, + eleneg=2.02, + eleaffin=0.942363, + covrad=1.46, + atmrad=1.63, + vdwrad=0.0, + tboil=1837.0, + tmelt=544.59, + density=9.8, + eleconfig="[Xe] 4f14 5d10 6s2 6p3", + oxistates="5, 3*", + ionenergy=( + 7.2855, + 16.69, + 25.56, + 45.3, + 56.0, + 88.3, + ), + isotopes={209: Isotope(208.980383, 1.0, 209)}, + ), + Element( + 84, + "Po", + "Polonium", + group=16, + period=6, + block="p", + series=5, + mass=208.982416, + eleneg=2.0, + eleaffin=1.9, + covrad=1.46, + atmrad=1.53, + vdwrad=0.0, + tboil=0.0, + tmelt=527.0, + density=9.2, + eleconfig="[Xe] 4f14 5d10 6s2 6p4", + oxistates="6, 4*, 2", + ionenergy=(8.414,), + isotopes={209: Isotope(208.982416, 1.0, 209)}, + ), + Element( + 85, + "At", + "Astatine", + group=17, + period=6, + block="p", + series=6, + mass=209.9871, + eleneg=2.2, + eleaffin=2.8, + covrad=1.45, + atmrad=1.43, + vdwrad=0.0, + tboil=610.0, + tmelt=575.0, + density=0.0, + eleconfig="[Xe] 4f14 5d10 6s2 6p5", + oxistates="7, 5, 3, 1, -1*", ionenergy=(), - isotopes={210: Isotope(209.987131, 1.0, 210)}), - Element( - 86, 'Rn', 'Radon', - group=18, period=6, block='p', series=2, - mass=222.0176, eleneg=0.0, eleaffin=0.0, - covrad=0.0, atmrad=1.34, vdwrad=0.0, - tboil=211.4, tmelt=202.0, density=9.23, - eleconfig='[Xe] 4f14 5d10 6s2 6p6', - oxistates='2*', - ionenergy=(10.7485, ), - isotopes={222: Isotope(222.0175705, 1.0, 222)}), - Element( - 87, 'Fr', 'Francium', - group=1, period=7, block='s', series=3, - mass=223.0197307, eleneg=0.7, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=950.0, tmelt=300.0, density=0.0, - eleconfig='[Rn] 7s', - oxistates='1*', - ionenergy=(4.0727, ), - isotopes={223: Isotope(223.0197307, 1.0, 223)}), - Element( - 88, 'Ra', 'Radium', - group=2, period=7, block='s', series=4, - mass=226.025403, eleneg=0.9, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=1413.0, tmelt=973.0, density=5.5, - eleconfig='[Rn] 7s2', - oxistates='2*', - ionenergy=(5.2784, 10.147, ), - isotopes={226: Isotope(226.0254026, 1.0, 226)}), - Element( - 89, 'Ac', 'Actinium', - group=3, period=7, block='f', series=10, - mass=227.027747, eleneg=1.1, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=3470.0, tmelt=1324.0, density=10.07, - eleconfig='[Rn] 6d 7s2', - oxistates='3*', - ionenergy=(5.17, 12.1, ), - isotopes={227: Isotope(227.027747, 1.0, 227)}), - Element( - 90, 'Th', 'Thorium', - group=3, period=7, block='f', series=10, - mass=232.0381, eleneg=1.3, eleaffin=0.0, - covrad=1.65, atmrad=0.0, vdwrad=0.0, - tboil=5060.0, tmelt=2028.0, density=11.72, - eleconfig='[Rn] 6d2 7s2', - oxistates='4*', - ionenergy=(6.3067, 11.5, 20.0, 28.8, ), - isotopes={230: Isotope(230.0331338, 0.0, 230, 75.38, 'ky'), # kilo year - 232: Isotope(232.0380504, 1.0, 232, 14.05, 'Gy')}), # Giga year - Element( - 91, 'Pa', 'Protactinium', - group=3, period=7, block='f', series=10, - mass=231.03588, eleneg=1.5, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=4300.0, tmelt=1845.0, density=15.37, - eleconfig='[Rn] 5f2 6d 7s2', - oxistates='5*, 4', - ionenergy=(5.89, ), - isotopes={231: Isotope(231.0358789, 0.0, 231, 32.76, 'ky'), - 233: Isotope(233.0402473, 0.0, 233, 26.967, 'd')}), # day - Element( - 92, 'U', 'Uranium', - group=3, period=7, block='f', series=10, - mass=238.02891, eleneg=1.38, eleaffin=0.0, - covrad=1.42, atmrad=0.0, vdwrad=1.86, - tboil=4407.0, tmelt=1408.0, density=18.97, - eleconfig='[Rn] 5f3 6d 7s2', - oxistates='6*, 5, 4, 3', - ionenergy=(6.1941, ), - isotopes={233: Isotope(233.0396352, 0.000, 233, 159.2, 'ky'), # kilo year - 234: Isotope(234.0409521, 0.0055, 234, 245.5, 'ky'), - # Mega year - 235: Isotope(235.0439299, 0.0072, 235, 704, 'My'), - # Mega year - 236: Isotope(236.0455680, 0.00, 236, 23.42, 'My'), - 237: Isotope(237.0487302, 0.00, 237, 6.75, 'd'), # day - 238: Isotope(238.0507826, 0.992745, 238, 4.468, 'Gy')}), # Giga year - Element( - 93, 'Np', 'Neptunium', - group=3, period=7, block='f', series=10, - mass=237.048167, eleneg=1.36, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=4175.0, tmelt=912.0, density=20.48, - eleconfig='[Rn] 5f4 6d 7s2', - oxistates='6, 5*, 4, 3', - ionenergy=(6.2657, ), - isotopes={237: Isotope(237.0481673, 0.0, 237, 2.144, 'My'), # Mega year - 238: Isotope(238.0509464, 0.0, 238, 2.117, 'd'), - 239: Isotope(239.0529390, 0.0, 239, 2.356, 'd')}), - Element( - 94, 'Pu', 'Plutonium', - group=3, period=7, block='f', series=10, - mass=244.064198, eleneg=1.28, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=3505.0, tmelt=913.0, density=19.74, - eleconfig='[Rn] 5f6 7s2', - oxistates='6, 5, 4*, 3', - ionenergy=(6.026, ), - isotopes={236: Isotope(236.0460580, 0.0, 236, 2.858, 'y'), - 238: Isotope(238.0495599, 0.0, 238, 87.7, 'y'), - # kilo year - 239: Isotope(239.0521634, 0.0, 239, 24.11, 'ky'), - 240: Isotope(240.0538135, 0.0, 240, 6.564, 'ky'), - 241: Isotope(241.0568515, 0.0, 241, 14.35, 'y'), - 242: Isotope(242.0587426, 0.0, 242, 375.0, 'ky'), - 244: Isotope(244.0642039, 0.0, 244, 80.0, 'My')}), # Mega year - Element( - 95, 'Am', 'Americium', - group=3, period=7, block='f', series=10, - mass=243.061373, eleneg=1.3, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=2880.0, tmelt=1449.0, density=13.67, - eleconfig='[Rn] 5f7 7s2', - oxistates='6, 5, 4, 3*', - ionenergy=(5.9738, ), - isotopes={243: Isotope(243.0613727, 0.0, 243), - 242: Isotope(242.0595492, 0.0, 242), - 241: Isotope(241.0568291, 0.0, 241)}), - Element( - 96, 'Cm', 'Curium', - group=3, period=7, block='f', series=10, - mass=247.070347, eleneg=1.3, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=1620.0, density=13.51, - eleconfig='[Rn] 5f7 6d 7s2', - oxistates='4, 3*', - ionenergy=(5.9914, ), - isotopes={247: Isotope(247.070347, 0.0, 247), - 242: Isotope(242.0588358, 0.0, 242), - 243: Isotope(243.0613891, 0.0, 243), - 244: Isotope(244.0627526, 0.0, 244), - 245: Isotope(245.0654912, 0.0, 245), - 246: Isotope(246.0672237, 0.0, 246)}), - Element( - 97, 'Bk', 'Berkelium', - group=3, period=7, block='f', series=10, - mass=247.070299, eleneg=1.3, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=1258.0, density=13.25, - eleconfig='[Rn] 5f9 7s2', - oxistates='4, 3*', - ionenergy=(6.1979, ), - isotopes={247: Isotope(247.070299, 1.0, 247)}), - Element( - 98, 'Cf', 'Californium', - group=3, period=7, block='f', series=10, - mass=251.07958, eleneg=1.3, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=1172.0, density=15.1, - eleconfig='[Rn] 5f10 7s2', - oxistates='4, 3*', - ionenergy=(6.2817, ), - isotopes={251: Isotope(251.07958, 1.0, 251)}), - Element( - 99, 'Es', 'Einsteinium', - group=3, period=7, block='f', series=10, - mass=252.08297, eleneg=1.3, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=1130.0, density=0.0, - eleconfig='[Rn] 5f11 7s2', - oxistates='3*', - ionenergy=(6.42, ), - isotopes={252: Isotope(252.08297, 1.0, 252)}), - Element( - 100, 'Fm', 'Fermium', - group=3, period=7, block='f', series=10, - mass=257.095099, eleneg=1.3, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=1800.0, density=0.0, - eleconfig='[Rn] 5f12 7s2', - oxistates='3*', - ionenergy=(6.5, ), - isotopes={257: Isotope(257.095099, 1.0, 257)}), - Element( - 101, 'Md', 'Mendelevium', - group=3, period=7, block='f', series=10, - mass=258.098425, eleneg=1.3, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=1100.0, density=0.0, - eleconfig='[Rn] 5f13 7s2', - oxistates='3*', - ionenergy=(6.58, ), - isotopes={258: Isotope(258.098425, 1.0, 258)}), - Element( - 102, 'No', 'Nobelium', - group=3, period=7, block='f', series=10, - mass=259.10102, eleneg=1.3, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=1100.0, density=0.0, - eleconfig='[Rn] 5f14 7s2', - oxistates='3, 2*', - ionenergy=(6.65, ), - isotopes={259: Isotope(259.10102, 1.0, 259)}), - Element( - 103, 'Lr', 'Lawrencium', - group=3, period=7, block='d', series=10, - mass=262.10969, eleneg=1.3, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=1900.0, density=0.0, - eleconfig='[Rn] 5f14 6d 7s2', - oxistates='3*', - ionenergy=(4.9, ), - isotopes={262: Isotope(262.10969, 1.0, 262)}), - Element( - 104, 'Rf', 'Rutherfordium', - group=4, period=7, block='d', series=8, - mass=261.10875, eleneg=0.0, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=0.0, density=0.0, - eleconfig='[Rn] 5f14 6d2 7s2', - oxistates='*', - ionenergy=(6.0, ), - isotopes={261: Isotope(261.10875, 1.0, 261)}), - Element( - 105, 'Db', 'Dubnium', - group=5, period=7, block='d', series=8, - mass=262.11415, eleneg=0.0, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=0.0, density=0.0, - eleconfig='[Rn] 5f14 6d3 7s2', - oxistates='*', + isotopes={210: Isotope(209.987131, 1.0, 210)}, + ), + Element( + 86, + "Rn", + "Radon", + group=18, + period=6, + block="p", + series=2, + mass=222.0176, + eleneg=0.0, + eleaffin=0.0, + covrad=0.0, + atmrad=1.34, + vdwrad=0.0, + tboil=211.4, + tmelt=202.0, + density=9.23, + eleconfig="[Xe] 4f14 5d10 6s2 6p6", + oxistates="2*", + ionenergy=(10.7485,), + isotopes={222: Isotope(222.0175705, 1.0, 222)}, + ), + Element( + 87, + "Fr", + "Francium", + group=1, + period=7, + block="s", + series=3, + mass=223.0197307, + eleneg=0.7, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=950.0, + tmelt=300.0, + density=0.0, + eleconfig="[Rn] 7s", + oxistates="1*", + ionenergy=(4.0727,), + isotopes={223: Isotope(223.0197307, 1.0, 223)}, + ), + Element( + 88, + "Ra", + "Radium", + group=2, + period=7, + block="s", + series=4, + mass=226.025403, + eleneg=0.9, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=1413.0, + tmelt=973.0, + density=5.5, + eleconfig="[Rn] 7s2", + oxistates="2*", + ionenergy=( + 5.2784, + 10.147, + ), + isotopes={226: Isotope(226.0254026, 1.0, 226)}, + ), + Element( + 89, + "Ac", + "Actinium", + group=3, + period=7, + block="f", + series=10, + mass=227.027747, + eleneg=1.1, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=3470.0, + tmelt=1324.0, + density=10.07, + eleconfig="[Rn] 6d 7s2", + oxistates="3*", + ionenergy=( + 5.17, + 12.1, + ), + isotopes={227: Isotope(227.027747, 1.0, 227)}, + ), + Element( + 90, + "Th", + "Thorium", + group=3, + period=7, + block="f", + series=10, + mass=232.0381, + eleneg=1.3, + eleaffin=0.0, + covrad=1.65, + atmrad=0.0, + vdwrad=0.0, + tboil=5060.0, + tmelt=2028.0, + density=11.72, + eleconfig="[Rn] 6d2 7s2", + oxistates="4*", + ionenergy=( + 6.3067, + 11.5, + 20.0, + 28.8, + ), + isotopes={ + 230: Isotope(230.0331338, 0.0, 230, 75.38, "ky"), # kilo year + 232: Isotope(232.0380504, 1.0, 232, 14.05, "Gy"), + }, + ), # Giga year + Element( + 91, + "Pa", + "Protactinium", + group=3, + period=7, + block="f", + series=10, + mass=231.03588, + eleneg=1.5, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=4300.0, + tmelt=1845.0, + density=15.37, + eleconfig="[Rn] 5f2 6d 7s2", + oxistates="5*, 4", + ionenergy=(5.89,), + isotopes={ + 231: Isotope(231.0358789, 0.0, 231, 32.76, "ky"), + 233: Isotope(233.0402473, 0.0, 233, 26.967, "d"), + }, + ), # day + Element( + 92, + "U", + "Uranium", + group=3, + period=7, + block="f", + series=10, + mass=238.02891, + eleneg=1.38, + eleaffin=0.0, + covrad=1.42, + atmrad=0.0, + vdwrad=1.86, + tboil=4407.0, + tmelt=1408.0, + density=18.97, + eleconfig="[Rn] 5f3 6d 7s2", + oxistates="6*, 5, 4, 3", + ionenergy=(6.1941,), + isotopes={ + 233: Isotope(233.0396352, 0.000, 233, 159.2, "ky"), # kilo year + 234: Isotope(234.0409521, 0.0055, 234, 245.5, "ky"), + # Mega year + 235: Isotope(235.0439299, 0.0072, 235, 704, "My"), + # Mega year + 236: Isotope(236.0455680, 0.00, 236, 23.42, "My"), + 237: Isotope(237.0487302, 0.00, 237, 6.75, "d"), # day + 238: Isotope(238.0507826, 0.992745, 238, 4.468, "Gy"), + }, + ), # Giga year + Element( + 93, + "Np", + "Neptunium", + group=3, + period=7, + block="f", + series=10, + mass=237.048167, + eleneg=1.36, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=4175.0, + tmelt=912.0, + density=20.48, + eleconfig="[Rn] 5f4 6d 7s2", + oxistates="6, 5*, 4, 3", + ionenergy=(6.2657,), + isotopes={ + 237: Isotope(237.0481673, 0.0, 237, 2.144, "My"), # Mega year + 238: Isotope(238.0509464, 0.0, 238, 2.117, "d"), + 239: Isotope(239.0529390, 0.0, 239, 2.356, "d"), + }, + ), + Element( + 94, + "Pu", + "Plutonium", + group=3, + period=7, + block="f", + series=10, + mass=244.064198, + eleneg=1.28, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=3505.0, + tmelt=913.0, + density=19.74, + eleconfig="[Rn] 5f6 7s2", + oxistates="6, 5, 4*, 3", + ionenergy=(6.026,), + isotopes={ + 236: Isotope(236.0460580, 0.0, 236, 2.858, "y"), + 238: Isotope(238.0495599, 0.0, 238, 87.7, "y"), + # kilo year + 239: Isotope(239.0521634, 0.0, 239, 24.11, "ky"), + 240: Isotope(240.0538135, 0.0, 240, 6.564, "ky"), + 241: Isotope(241.0568515, 0.0, 241, 14.35, "y"), + 242: Isotope(242.0587426, 0.0, 242, 375.0, "ky"), + 244: Isotope(244.0642039, 0.0, 244, 80.0, "My"), + }, + ), # Mega year + Element( + 95, + "Am", + "Americium", + group=3, + period=7, + block="f", + series=10, + mass=243.061373, + eleneg=1.3, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=2880.0, + tmelt=1449.0, + density=13.67, + eleconfig="[Rn] 5f7 7s2", + oxistates="6, 5, 4, 3*", + ionenergy=(5.9738,), + isotopes={ + 243: Isotope(243.0613727, 0.0, 243), + 242: Isotope(242.0595492, 0.0, 242), + 241: Isotope(241.0568291, 0.0, 241), + }, + ), + Element( + 96, + "Cm", + "Curium", + group=3, + period=7, + block="f", + series=10, + mass=247.070347, + eleneg=1.3, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=1620.0, + density=13.51, + eleconfig="[Rn] 5f7 6d 7s2", + oxistates="4, 3*", + ionenergy=(5.9914,), + isotopes={ + 247: Isotope(247.070347, 0.0, 247), + 242: Isotope(242.0588358, 0.0, 242), + 243: Isotope(243.0613891, 0.0, 243), + 244: Isotope(244.0627526, 0.0, 244), + 245: Isotope(245.0654912, 0.0, 245), + 246: Isotope(246.0672237, 0.0, 246), + }, + ), + Element( + 97, + "Bk", + "Berkelium", + group=3, + period=7, + block="f", + series=10, + mass=247.070299, + eleneg=1.3, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=1258.0, + density=13.25, + eleconfig="[Rn] 5f9 7s2", + oxistates="4, 3*", + ionenergy=(6.1979,), + isotopes={247: Isotope(247.070299, 1.0, 247)}, + ), + Element( + 98, + "Cf", + "Californium", + group=3, + period=7, + block="f", + series=10, + mass=251.07958, + eleneg=1.3, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=1172.0, + density=15.1, + eleconfig="[Rn] 5f10 7s2", + oxistates="4, 3*", + ionenergy=(6.2817,), + isotopes={251: Isotope(251.07958, 1.0, 251)}, + ), + Element( + 99, + "Es", + "Einsteinium", + group=3, + period=7, + block="f", + series=10, + mass=252.08297, + eleneg=1.3, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=1130.0, + density=0.0, + eleconfig="[Rn] 5f11 7s2", + oxistates="3*", + ionenergy=(6.42,), + isotopes={252: Isotope(252.08297, 1.0, 252)}, + ), + Element( + 100, + "Fm", + "Fermium", + group=3, + period=7, + block="f", + series=10, + mass=257.095099, + eleneg=1.3, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=1800.0, + density=0.0, + eleconfig="[Rn] 5f12 7s2", + oxistates="3*", + ionenergy=(6.5,), + isotopes={257: Isotope(257.095099, 1.0, 257)}, + ), + Element( + 101, + "Md", + "Mendelevium", + group=3, + period=7, + block="f", + series=10, + mass=258.098425, + eleneg=1.3, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=1100.0, + density=0.0, + eleconfig="[Rn] 5f13 7s2", + oxistates="3*", + ionenergy=(6.58,), + isotopes={258: Isotope(258.098425, 1.0, 258)}, + ), + Element( + 102, + "No", + "Nobelium", + group=3, + period=7, + block="f", + series=10, + mass=259.10102, + eleneg=1.3, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=1100.0, + density=0.0, + eleconfig="[Rn] 5f14 7s2", + oxistates="3, 2*", + ionenergy=(6.65,), + isotopes={259: Isotope(259.10102, 1.0, 259)}, + ), + Element( + 103, + "Lr", + "Lawrencium", + group=3, + period=7, + block="d", + series=10, + mass=262.10969, + eleneg=1.3, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=1900.0, + density=0.0, + eleconfig="[Rn] 5f14 6d 7s2", + oxistates="3*", + ionenergy=(4.9,), + isotopes={262: Isotope(262.10969, 1.0, 262)}, + ), + Element( + 104, + "Rf", + "Rutherfordium", + group=4, + period=7, + block="d", + series=8, + mass=261.10875, + eleneg=0.0, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=0.0, + density=0.0, + eleconfig="[Rn] 5f14 6d2 7s2", + oxistates="*", + ionenergy=(6.0,), + isotopes={261: Isotope(261.10875, 1.0, 261)}, + ), + Element( + 105, + "Db", + "Dubnium", + group=5, + period=7, + block="d", + series=8, + mass=262.11415, + eleneg=0.0, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=0.0, + density=0.0, + eleconfig="[Rn] 5f14 6d3 7s2", + oxistates="*", ionenergy=(), - isotopes={262: Isotope(262.11415, 1.0, 262)}), - Element( - 106, 'Sg', 'Seaborgium', - group=6, period=7, block='d', series=8, - mass=266.12193, eleneg=0.0, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=0.0, density=0.0, - eleconfig='[Rn] 5f14 6d4 7s2', - oxistates='*', + isotopes={262: Isotope(262.11415, 1.0, 262)}, + ), + Element( + 106, + "Sg", + "Seaborgium", + group=6, + period=7, + block="d", + series=8, + mass=266.12193, + eleneg=0.0, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=0.0, + density=0.0, + eleconfig="[Rn] 5f14 6d4 7s2", + oxistates="*", ionenergy=(), - isotopes={266: Isotope(266.12193, 1.0, 266)}), - Element( - 107, 'Bh', 'Bohrium', - group=7, period=7, block='d', series=8, - mass=264.12473, eleneg=0.0, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=0.0, density=0.0, - eleconfig='[Rn] 5f14 6d5 7s2', - oxistates='*', + isotopes={266: Isotope(266.12193, 1.0, 266)}, + ), + Element( + 107, + "Bh", + "Bohrium", + group=7, + period=7, + block="d", + series=8, + mass=264.12473, + eleneg=0.0, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=0.0, + density=0.0, + eleconfig="[Rn] 5f14 6d5 7s2", + oxistates="*", ionenergy=(), - isotopes={264: Isotope(264.12473, 1.0, 264)}), - Element( - 108, 'Hs', 'Hassium', - group=8, period=7, block='d', series=8, - mass=269.13411, eleneg=0.0, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=0.0, density=0.0, - eleconfig='[Rn] 5f14 6d6 7s2', - oxistates='*', + isotopes={264: Isotope(264.12473, 1.0, 264)}, + ), + Element( + 108, + "Hs", + "Hassium", + group=8, + period=7, + block="d", + series=8, + mass=269.13411, + eleneg=0.0, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=0.0, + density=0.0, + eleconfig="[Rn] 5f14 6d6 7s2", + oxistates="*", ionenergy=(), - isotopes={269: Isotope(269.13411, 1.0, 269)}), - Element( - 109, 'Mt', 'Meitnerium', - group=9, period=7, block='d', series=8, - mass=268.13882, eleneg=0.0, eleaffin=0.0, - covrad=0.0, atmrad=0.0, vdwrad=0.0, - tboil=0.0, tmelt=0.0, density=0.0, - eleconfig='[Rn] 5f14 6d7 7s2', - oxistates='*', + isotopes={269: Isotope(269.13411, 1.0, 269)}, + ), + Element( + 109, + "Mt", + "Meitnerium", + group=9, + period=7, + block="d", + series=8, + mass=268.13882, + eleneg=0.0, + eleaffin=0.0, + covrad=0.0, + atmrad=0.0, + vdwrad=0.0, + tboil=0.0, + tmelt=0.0, + density=0.0, + eleconfig="[Rn] 5f14 6d7 7s2", + oxistates="*", ionenergy=(), - isotopes={268: Isotope(268.13882, 1.0, 268)})) + isotopes={268: Isotope(268.13882, 1.0, 268)}, + ), +) -PERIODS = {1: 'K', 2: 'L', 3: 'M', 4: 'N', 5: 'O', 6: 'P', 7: 'Q'} +PERIODS = {1: "K", 2: "L", 3: "M", 4: "N", 5: "O", 6: "P", 7: "Q"} -BLOCKS = {'s': '', 'g': '', 'f': '', 'd': '', 'p': ''} +BLOCKS = {"s": "", "g": "", "f": "", "d": "", "p": ""} GROUPS = { - 1: ('IA', 'Alkali metals'), - 2: ('IIA', 'Alkaline earths'), - 3: ('IIIB', ''), - 4: ('IVB', ''), - 5: ('VB', ''), - 6: ('VIB', ''), - 7: ('VIIB', ''), - 8: ('VIIIB', ''), - 9: ('VIIIB', ''), - 10: ('VIIIB', ''), - 11: ('IB', 'Coinage metals'), - 12: ('IIB', ''), - 13: ('IIIA', 'Boron group'), - 14: ('IVA', 'Carbon group'), - 15: ('VA', 'Pnictogens'), - 16: ('VIA', 'Chalcogens'), - 17: ('VIIA', 'Halogens'), - 18: ('VIIIA', 'Noble gases')} + 1: ("IA", "Alkali metals"), + 2: ("IIA", "Alkaline earths"), + 3: ("IIIB", ""), + 4: ("IVB", ""), + 5: ("VB", ""), + 6: ("VIB", ""), + 7: ("VIIB", ""), + 8: ("VIIIB", ""), + 9: ("VIIIB", ""), + 10: ("VIIIB", ""), + 11: ("IB", "Coinage metals"), + 12: ("IIB", ""), + 13: ("IIIA", "Boron group"), + 14: ("IVA", "Carbon group"), + 15: ("VA", "Pnictogens"), + 16: ("VIA", "Chalcogens"), + 17: ("VIIA", "Halogens"), + 18: ("VIIIA", "Noble gases"), +} SERIES = { - 1: 'Nonmetals', - 2: 'Noble gases', - 3: 'Alkali metals', - 4: 'Alkaline earth metals', - 5: 'Metalloids', - 6: 'Halogens', - 7: 'Poor metals', - 8: 'Transition metals', - 9: 'Lanthanides', - 10: 'Actinides'} + 1: "Nonmetals", + 2: "Noble gases", + 3: "Alkali metals", + 4: "Alkaline earth metals", + 5: "Metalloids", + 6: "Halogens", + 7: "Poor metals", + 8: "Transition metals", + 9: "Lanthanides", + 10: "Actinides", +} def _descriptions(symbol): """Delay load descriptions.""" e = ELEMENTS - e['H'].description = ( + e["H"].description = ( "Colourless, odourless gaseous chemical element. Lightest and " "most abundant element in the universe. Present in water and in " "all organic compounds. Chemically reacts with most elements. " - "Discovered by Henry Cavendish in 1776.") - e['He'].description = ( + "Discovered by Henry Cavendish in 1776." + ) + e["He"].description = ( "Colourless, odourless gaseous nonmetallic element. Belongs to " "group 18 of the periodic table. Lowest boiling point of all " "elements and can only be solidified under pressure. Chemically " "inert, no known compounds. Discovered in the solar spectrum in " - "1868 by Lockyer.") - e['Li'].description = ( + "1868 by Lockyer." + ) + e["Li"].description = ( "Socket silvery metal. First member of group 1 of the periodic " - "table. Lithium salts are used in psychomedicine.") - e['Be'].description = ( + "table. Lithium salts are used in psychomedicine." + ) + e["Be"].description = ( "Grey metallic element of group 2 of the periodic table. Is toxic " "and can cause severe lung diseases and dermatitis. Shows high " "covalent character. It was isolated independently by F. Wohler " - "and A.A. Bussy in 1828.") - e['B'].description = ( + "and A.A. Bussy in 1828." + ) + e["B"].description = ( "An element of group 13 of the periodic table. There are two " "allotropes, amorphous boron is a brown power, but metallic boron " "is black. The metallic form is hard (9.3 on Mohs' scale) and a " "bad conductor in room temperatures. It is never found free in " "nature. Boron-10 is used in nuclear reactor control rods and " "shields. It was discovered in 1808 by Sir Humphry Davy and by " - "J.L. Gay-Lussac and L.J. Thenard.") - e['C'].description = ( + "J.L. Gay-Lussac and L.J. Thenard." + ) + e["C"].description = ( "Carbon is a member of group 14 of the periodic table. It has " "three allotropic forms of it, diamonds, graphite and fullerite. " "Carbon-14 is commonly used in radioactive dating. Carbon occurs " "in all organic life and is the basis of organic chemistry. Carbon " "has the interesting chemical property of being able to bond with " - "itself, and a wide variety of other elements.") - e['N'].description = ( + "itself, and a wide variety of other elements." + ) + e["N"].description = ( "Colourless, gaseous element which belongs to group 15 of the " "periodic table. Constitutes ~78% of the atmosphere and is an " "essential part of the ecosystem. Nitrogen for industrial purposes " "is acquired by the fractional distillation of liquid air. " "Chemically inactive, reactive generally only at high temperatures " "or in electrical discharges. It was discovered in 1772 by D. " - "Rutherford.") - e['O'].description = ( + "Rutherford." + ) + e["O"].description = ( "A colourless, odourless gaseous element belonging to group 16 of " "the periodic table. It is the most abundant element present in " "the earth's crust. It also makes up 20.8% of the Earth's " @@ -1912,87 +4006,102 @@ def _descriptions(symbol): "air by fractional distillation. It is used in high temperature " "welding, and in breathing. It commonly comes in the form of " "Oxygen, but is found as Ozone in the upper atmosphere. It was " - "discovered by Priestley in 1774.") - e['F'].description = ( + "discovered by Priestley in 1774." + ) + e["F"].description = ( "A poisonous pale yellow gaseous element belonging to group 17 of " "the periodic table (The halogens). It is the most chemically " "reactive and electronegative element. It is highly dangerous, " "causing severe chemical burns on contact with flesh. Fluorine was " "identified by Scheele in 1771 and first isolated by Moissan in " - "1886.") - e['Ne'].description = ( + "1886." + ) + e["Ne"].description = ( "Colourless gaseous element of group 18 on the periodic table " "(noble gases). Neon occurs in the atmosphere, and comprises " "0.0018% of the volume of the atmosphere. It has a distinct " "reddish glow when used in discharge tubes and neon based lamps. " "It forms almost no chemical compounds. Neon was discovered in " - "1898 by Sir William Ramsey and M.W. Travers.") - e['Na'].description = ( + "1898 by Sir William Ramsey and M.W. Travers." + ) + e["Na"].description = ( "Soft silvery reactive element belonging to group 1 of the " "periodic table (alkali metals). It is highly reactive, oxidizing " "in air and reacting violently with water, forcing it to be kept " - "under oil. It was first isolated by Humphrey Davy in 1807.") - e['Mg'].description = ( + "under oil. It was first isolated by Humphrey Davy in 1807." + ) + e["Mg"].description = ( "Silvery metallic element belonging to group 2 of the periodic " "table (alkaline-earth metals). It is essential for living " "organisms, and is used in a number of light alloys. Chemically " "very reactive, it forms a protective oxide coating when exposed " "to air and burns with an intense white flame. It also reacts with " "sulphur, nitrogen and the halogens. First isolated by Bussy in " - "1828.") - e['Al'].description = ( + "1828." + ) + e["Al"].description = ( "Silvery-white lustrous metallic element of group 3 of the " "periodic table. Highly reactive but protected by a thin " "transparent layer of the oxide which quickly forms in air. There " "are many alloys of aluminum, as well as a good number of " "industrial uses. Makes up 8.1% of the Earth's crust, by weight. " - "Isolated in 1825 by H.C. Oersted.") - e['Si'].description = ( + "Isolated in 1825 by H.C. Oersted." + ) + e["Si"].description = ( "Metalloid element belonging to group 14 of the periodic table. " "It is the second most abundant element in the Earth's crust, " "making up 25.7% of it by weight. Chemically less reactive than " "carbon. First identified by Lavoisier in 1787 and first isolated " - "in 1823 by Berzelius.") - e['P'].description = ( + "in 1823 by Berzelius." + ) + e["P"].description = ( "Non-metallic element belonging to group 15 of the periodic " "table. Has a multiple allotropic forms. Essential element for " - "living organisms. It was discovered by Brandt in 1669.") - e['S'].description = ( + "living organisms. It was discovered by Brandt in 1669." + ) + e["S"].description = ( "Yellow, nonmetallic element belonging to group 16 of the " "periodic table. It is an essential element in living organisms, " "needed in the amino acids cysteine and methionine, and hence in " - "many proteins. Absorbed by plants from the soil as sulphate ion.") - e['Cl'].description = ( + "many proteins. Absorbed by plants from the soil as sulphate ion." + ) + e["Cl"].description = ( "Halogen element. Poisonous greenish-yellow gas. Occurs widely in " "nature as sodium chloride in seawater. Reacts directly with many " "elements and compounds, strong oxidizing agent. Discovered by " "Karl Scheele in 1774. Humphrey David confirmed it as an element " - "in 1810.") - e['Ar'].description = ( + "in 1810." + ) + e["Ar"].description = ( "Monatomic noble gas. Makes up 0.93% of the air. Colourless, " "odorless. Is inert and has no true compounds. Lord Rayleigh and " - "Sir william Ramsey identified argon in 1894.") - e['K'].description = ( + "Sir william Ramsey identified argon in 1894." + ) + e["K"].description = ( "Soft silvery metallic element belonging to group 1 of the " "periodic table (alkali metals). Occurs naturally in seawater and " "a many minerals. Highly reactive, chemically, it resembles sodium " "in its behavior and compounds. Discovered by Sir Humphry Davy in " - "1807.") - e['Ca'].description = ( + "1807." + ) + e["Ca"].description = ( "Soft grey metallic element belonging to group 2 of the periodic " "table. Used a reducing agent in the extraction of thorium, " - "zirconium and uranium. Essential element for living organisms.") - e['Sc'].description = ( + "zirconium and uranium. Essential element for living organisms." + ) + e["Sc"].description = ( "Rare soft silvery metallic element belonging to group 3 of the " "periodic table. There are ten isotopes, nine of which are " "radioactive and have short half-lives. Predicted in 1869 by " - "Mendeleev, isolated by Nilson in 1879.") - e['Ti'].description = ( + "Mendeleev, isolated by Nilson in 1879." + ) + e["Ti"].description = ( "White metallic transition element. Occurs in numerous minerals. " "Used in strong, light corrosion-resistant alloys. Forms a passive " "oxide coating when exposed to air. First discovered by Gregor in " - "1789.") - e['V'].description = ( + "1789." + ) + e["V"].description = ( "Soft and ductile, bright white metal. Good resistance to " "corrosion by alkalis, sulphuric and hydrochloric acid. It " "oxidizes readily about 933K. There are two naturally occurring " @@ -2008,68 +4117,81 @@ def _descriptions(symbol): "Silvery-white metallic transition element. Vanadium is essential " "to Ascidians. Rats and chickens are also known to require it. " "Metal powder is a fire hazard, and vanadium compounds should be " - "considered highly toxic. May cause lung cancer if inhaled.") - e['Cr'].description = ( + "considered highly toxic. May cause lung cancer if inhaled." + ) + e["Cr"].description = ( "Hard silvery transition element. Used in decorative " - "electroplating. Discovered in 1797 by Vauquelin.") - e['Mn'].description = ( + "electroplating. Discovered in 1797 by Vauquelin." + ) + e["Mn"].description = ( "Grey brittle metallic transition element. Rather " "electropositive, combines with some non-metals when heated. " - "Discovered in 1774 by Scheele.") - e['Fe'].description = ( + "Discovered in 1774 by Scheele." + ) + e["Fe"].description = ( "Silvery malleable and ductile metallic transition element. Has " "nine isotopes and is the fourth most abundant element in the " "earth's crust. Required by living organisms as a trace element " "(used in hemoglobin in humans.) Quite reactive, oxidizes in moist " "air, displaces hydrogen from dilute acids and combines with " - "nonmetallic elements.") - e['Co'].description = ( + "nonmetallic elements." + ) + e["Co"].description = ( "Light grey transition element. Some meteorites contain small " "amounts of metallic cobalt. Generally alloyed for use. Mammals " "require small amounts of cobalt salts. Cobalt-60, an artificially " "produced radioactive isotope of Cobalt is an important " "radioactive tracer and cancer-treatment agent. Discovered by G. " - "Brandt in 1737.") - e['Ni'].description = ( + "Brandt in 1737." + ) + e["Ni"].description = ( "Malleable ductile silvery metallic transition element. " - "Discovered by A.F. Cronstedt in 1751.") - e['Cu'].description = ( + "Discovered by A.F. Cronstedt in 1751." + ) + e["Cu"].description = ( "Red-brown transition element. Known by the Romans as 'cuprum.' " "Extracted and used for thousands of years. Malleable, ductile and " "an excellent conductor of heat and electricity. When in moist " - "conditions, a greenish layer forms on the outside.") - e['Zn'].description = ( + "conditions, a greenish layer forms on the outside." + ) + e["Zn"].description = ( "Blue-white metallic element. Occurs in multiple compounds " "naturally. Five stable isotopes are six radioactive isotopes have " "been found. Chemically a reactive metal, combines with oxygen and " - "other non-metals, reacts with dilute acids to release hydrogen.") - e['Ga'].description = ( + "other non-metals, reacts with dilute acids to release hydrogen." + ) + e["Ga"].description = ( "Soft silvery metallic element, belongs to group 13 of the " "periodic table. The two stable isotopes are Ga-69 and Ga-71. " "Eight radioactive isotopes are known, all having short " "half-lives. Gallium Arsenide is used as a semiconductor. Corrodes " "most other metals by diffusing into their lattice. First " - "identified by Francois Lecoq de Boisbaudran in 1875.") - e['Ge'].description = ( + "identified by Francois Lecoq de Boisbaudran in 1875." + ) + e["Ge"].description = ( "Lustrous hard metalloid element, belongs to group 14 of the " "periodic table. Forms a large number of organometallic compounds. " "Predicted by Mendeleev in 1871, it was actually found in 1886 by " - "Winkler.") - e['As'].description = ( + "Winkler." + ) + e["As"].description = ( "Metalloid element of group 15. There are three allotropes, " "yellow, black, and grey. Reacts with halogens, concentrated " "oxidizing acids and hot alkalis. Albertus Magnus is believed to " - "have been the first to isolate the element in 1250.") - e['Se'].description = ( + "have been the first to isolate the element in 1250." + ) + e["Se"].description = ( "Metalloid element, belongs to group 16 of the periodic table. " "Multiple allotropic forms exist. Chemically resembles sulphur. " - "Discovered in 1817 by Jons J. Berzelius.") - e['Br'].description = ( + "Discovered in 1817 by Jons J. Berzelius." + ) + e["Br"].description = ( "Halogen element. Red volatile liquid at room temperature. Its " "reactivity is somewhere between chlorine and iodine. Harmful to " "human tissue in a liquid state, the vapour irritates eyes and " - "throat. Discovered in 1826 by Antoine Balard.") - e['Kr'].description = ( + "throat. Discovered in 1826 by Antoine Balard." + ) + e["Kr"].description = ( "Colorless gaseous element, belongs to the noble gases. Occurs in " "the air, 0.0001% by volume. It can be extracted from liquid air " "by fractional distillation. Generally not isolated, but used with " @@ -2077,37 +4199,43 @@ def _descriptions(symbol): "and five radioactive isotopes. Kr-85, the most stable radioactive " "isotope, has a half-life of 10.76 years and is produced in " "fission reactors. Practically inert, though known to form " - "compounds with Fluorine.") - e['Rb'].description = ( + "compounds with Fluorine." + ) + e["Rb"].description = ( "Soft silvery metallic element, belongs to group 1 of the " "periodic table. Rb-97, the naturally occurring isotope, is " "radioactive. It is highly reactive, with properties similar to " "other elements in group 1, like igniting spontaneously in air. " "Discovered spectroscopically in 1861 by W. Bunsen and G.R. " - "Kirchoff.") - e['Sr'].description = ( + "Kirchoff." + ) + e["Sr"].description = ( "Soft yellowish metallic element, belongs to group 2 of the " "periodic table. Highly reactive chemically. Sr-90 is present in " "radioactive fallout and has a half-life of 28 years. Discovered " - "in 1798 by Klaproth and Hope, isolated in 1808 by Humphry Davy.") - e['Y'].description = ( + "in 1798 by Klaproth and Hope, isolated in 1808 by Humphry Davy." + ) + e["Y"].description = ( "Silvery-grey metallic element of group 3 on the periodic table. " "Found in uranium ores. The only natural isotope is Y-89, there " "are 14 other artificial isotopes. Chemically resembles the " "lanthanoids. Stable in the air below 400 degrees, celsius. " - "Discovered in 1828 by Friedrich Wohler.") - e['Zr'].description = ( + "Discovered in 1828 by Friedrich Wohler." + ) + e["Zr"].description = ( "Grey-white metallic transition element. Five natural isotopes " "and six radioactive isotopes are known. Used in nuclear reactors " "for a Neutron absorber. Discovered in 1789 by Martin Klaproth, " - "isolated in 1824 by Berzelius.") - e['Nb'].description = ( + "isolated in 1824 by Berzelius." + ) + e["Nb"].description = ( "Soft, ductile grey-blue metallic transition element. Used in " "special steels and in welded joints to increase strength. " "Combines with halogens and oxidizes in air at 200 degrees " "celsius. Discovered by Charles Hatchett in 1801 and isolated by " - "Blomstrand in 1864. Called Columbium originally.") - e['Mo'].description = ( + "Blomstrand in 1864. Called Columbium originally." + ) + e["Mo"].description = ( "Silvery-white, hard metallic transition element. It is " "chemically unreactive and is not affected by most acids. It " "oxidizes at high temperatures. There are seven natural isotopes, " @@ -2120,75 +4248,88 @@ def _descriptions(symbol): "which means lead. Trace amounts of molybdenum are required for " "all known forms of life. All molybdenum compounds should be " "considered highly toxic, and will also cause severe birth " - "defects.") - e['Tc'].description = ( + "defects." + ) + e["Tc"].description = ( "Radioactive metallic transition element. Can be detected in some " "stars and the fission products of uranium. First made by Perrier " "and Segre by bombarding molybdenum with deutrons, giving them " "Tc-97. Tc-99 is the most stable isotope with a half-life of " "2.6*10^6 years. Sixteen isotopes are known. Organic technetium " "compounds are used in bone imaging. Chemical properties are " - "intermediate between rhenium and manganese.") - e['Ru'].description = ( + "intermediate between rhenium and manganese." + ) + e["Ru"].description = ( "Hard white metallic transition element. Found with platinum, " "used as a catalyst in some platinum alloys. Dissolves in fused " "alkalis, and is not attacked by acids. Reacts with halogens and " - "oxygen at high temperatures. Isolated in 1844 by K.K. Klaus.") - e['Rh'].description = ( + "oxygen at high temperatures. Isolated in 1844 by K.K. Klaus." + ) + e["Rh"].description = ( "Silvery white metallic transition element. Found with platinum " "and used in some platinum alloys. Not attacked by acids, " "dissolves only in aqua regia. Discovered in 1803 by W.H. " - "Wollaston.") - e['Pd'].description = ( + "Wollaston." + ) + e["Pd"].description = ( "Soft white ductile transition element. Found with some copper " "and nickel ores. Does not react with oxygen at normal " "temperatures. Dissolves slowly in hydrochloric acid. Discovered " - "in 1803 by W.H. Wollaston.") - e['Ag'].description = ( + "in 1803 by W.H. Wollaston." + ) + e["Ag"].description = ( "White lustrous soft metallic transition element. Found in both " "its elemental form and in minerals. Used in jewellery, tableware " - "and so on. Less reactive than silver, chemically.") - e['Cd'].description = ( + "and so on. Less reactive than silver, chemically." + ) + e["Cd"].description = ( "Soft bluish metal belonging to group 12 of the periodic table. " "Extremely toxic even in low concentrations. Chemically similar to " "zinc, but lends itself to more complex compounds. Discovered in " - "1817 by F. Stromeyer.") - e['In'].description = ( + "1817 by F. Stromeyer." + ) + e["In"].description = ( "Soft silvery element belonging to group 13 of the periodic " "table. The most common natural isotope is In-115, which has a " "half-life of 6*10^4 years. Five other radioisotopes exist. " - "Discovered in 1863 by Reich and Richter.") - e['Sn'].description = ( + "Discovered in 1863 by Reich and Richter." + ) + e["Sn"].description = ( "Silvery malleable metallic element belonging to group 14 of the " "periodic table. Twenty-six isotopes are known, five of which are " "radioactive. Chemically reactive. Combines directly with chlorine " - "and oxygen and displaces hydrogen from dilute acids.") - e['Sb'].description = ( + "and oxygen and displaces hydrogen from dilute acids." + ) + e["Sb"].description = ( "Element of group 15. Multiple allotropic forms. The stable form " "of antimony is a blue-white metal. Yellow and black antimony are " "unstable non-metals. Used in flame-proofing, paints, ceramics, " "enamels, and rubber. Attacked by oxidizing acids and halogens. " - "First reported by Tholden in 1450.") - e['Te'].description = ( + "First reported by Tholden in 1450." + ) + e["Te"].description = ( "Silvery metalloid element of group 16. Eight natural isotopes, " "nine radioactive isotopes. Used in semiconductors and to a degree " "in some steels. Chemistry is similar to Sulphur. Discovered in " - "1782 by Franz Miller.") - e['I'].description = ( + "1782 by Franz Miller." + ) + e["I"].description = ( "Dark violet nonmetallic element, belongs to group 17 of the " "periodic table. Insoluble in water. Required as a trace element " "for living organisms. One stable isotope, I-127 exists, in " "addition to fourteen radioactive isotopes. Chemically the least " "reactive of the halogens, and the most electropositive metallic " - "halogen. Discovered in 1812 by Courtois.") - e['Xe'].description = ( + "halogen. Discovered in 1812 by Courtois." + ) + e["Xe"].description = ( "Colourless, odourless gas belonging to group 18 on the periodic " "table (the noble gases.) Nine natural isotopes and seven " "radioactive isotopes are known. Xenon was part of the first " "noble-gas compound synthesized. Several others involving Xenon " "have been found since then. Xenon was discovered by Ramsey and " - "Travers in 1898.") - e['Cs'].description = ( + "Travers in 1898." + ) + e["Cs"].description = ( "Soft silvery-white metallic element belonging to group 1 of the " "periodic table. One of the three metals which are liquid at room " "temperature. Cs-133 is the natural, and only stable, isotope. " @@ -2203,13 +4344,15 @@ def _descriptions(symbol): "spectroscopically. Its identification was based upon the bright " "blue lines in its spectrum. The name comes from the latin word " "caesius, which means sky blue. Caesium should be considered " - "highly toxic. Some of the radioisotopes are even more toxic.") - e['Ba'].description = ( + "highly toxic. Some of the radioisotopes are even more toxic." + ) + e["Ba"].description = ( "Silvery-white reactive element, belonging to group 2 of the " "periodic table. Soluble barium compounds are extremely poisonous. " "Identified in 1774 by Karl Scheele and extracted in 1808 by " - "Humphry Davy.") - e['La'].description = ( + "Humphry Davy." + ) + e["La"].description = ( "(From the Greek word lanthanein, to line hidden) Silvery " "metallic element belonging to group 3 of the periodic table and " "oft considered to be one of the lanthanoids. Found in some " @@ -2218,19 +4361,22 @@ def _descriptions(symbol): "10^15 years. The other twenty-three isotopes are radioactive. It " "resembles the lanthanoids chemically. Lanthanum has a low to " "moderate level of toxicity, and should be handled with care. " - "Discovered in 1839 by C.G. Mosander.") - e['Ce'].description = ( + "Discovered in 1839 by C.G. Mosander." + ) + e["Ce"].description = ( "Silvery metallic element, belongs to the lanthanoids. Four " "natural isotopes exist, and fifteen radioactive isotopes have " "been identified. Used in some rare-earth alloys. The oxidized " "form is used in the glass industry. Discovered by Martin .H. " - "Klaproth in 1803.") - e['Pr'].description = ( + "Klaproth in 1803." + ) + e["Pr"].description = ( "Soft silvery metallic element, belongs to the lanthanoids. Only " "natural isotope is Pr-141 which is not radioactive. Fourteen " "radioactive isotopes have been artificially produced. Used in " - "rare-earth alloys. Discovered in 1885 by C.A. von Welsbach.") - e['Nd'].description = ( + "rare-earth alloys. Discovered in 1885 by C.A. von Welsbach." + ) + e["Nd"].description = ( "Soft bright silvery metallic element, belongs to the " "lanthanoids. Seven natural isotopes, Nd-144 being the only " "radioactive one with a half-life of 10^10 to 10^15 years. Six " @@ -2246,8 +4392,9 @@ def _descriptions(symbol): "which means 'new twin'. Neodymium should be considered highly " "toxic, however evidence would seem to show that it acts as little " "more than a skin and eye irritant. The dust however, presents a " - "fire and explosion hazard.") - e['Pm'].description = ( + "fire and explosion hazard." + ) + e["Pm"].description = ( "Soft silvery metallic element, belongs to the lanthanoids. " "Pm-147, the only natural isotope, is radioactive and has a " "half-life of 252 years. Eighteen radioisotopes have been " @@ -2255,8 +4402,9 @@ def _descriptions(symbol): "nuclear decay waste. Pm-147 is of interest as a beta-decay " "source, however Pm-146 and Pm-148 have to be removed from it " "first, as they generate gamma radiation. Discovered by J.A. " - "Marinsky, L.E. Glendenin and C.D. Coryell in 1947.") - e['Sm'].description = ( + "Marinsky, L.E. Glendenin and C.D. Coryell in 1947." + ) + e["Sm"].description = ( "Soft silvery metallic element, belongs to the lanthanoids. Seven " "natural isotopes, Sm-147 is the only radioisotope, and has a " "half-life of 2.5*10^11 years. Used for making special alloys " @@ -2265,22 +4413,26 @@ def _descriptions(symbol): "special optical glasses. The largest use of the element is its " "ferromagnetic alloy which produces permanent magnets that are " "five times stronger than magnets produced by any other material. " - "Discovered by Francois Lecoq de Boisbaudran in 1879.") - e['Eu'].description = ( + "Discovered by Francois Lecoq de Boisbaudran in 1879." + ) + e["Eu"].description = ( "Soft silvery metallic element belonging to the lanthanoids. " "Eu-151 and Eu-153 are the only two stable isotopes, both of which " - "are Neutron absorbers. Discovered in 1889 by Sir William Crookes.") - e['Gd'].description = ( + "are Neutron absorbers. Discovered in 1889 by Sir William Crookes." + ) + e["Gd"].description = ( "Soft silvery metallic element belonging to the lanthanoids. " "Seven natural, stable isotopes are known in addition to eleven " "artificial isotopes. Gd-155 and Gd-157 and the best neutron " "absorbers of all elements. Gadolinium compounds are used in " - "electronics. Discovered by J.C.G Marignac in 1880.") - e['Tb'].description = ( + "electronics. Discovered by J.C.G Marignac in 1880." + ) + e["Tb"].description = ( "Silvery metallic element belonging to the lanthanoids. Tb-159 is " "the only stable isotope, there are seventeen artificial isotopes. " - "Discovered by G.G. Mosander in 1843.") - e['Dy'].description = ( + "Discovered by G.G. Mosander in 1843." + ) + e["Dy"].description = ( "Metallic with a bright silvery-white lustre. Dysprosium belongs " "to the lanthanoids. It is relatively stable in air at room " "temperatures, it will however dissolve in mineral acids, evolving " @@ -2290,8 +4442,9 @@ def _descriptions(symbol): "Dysprosium is used as a neutron absorber in nuclear fission " "reactions, and in compact disks. It was discovered by Paul Emile " "Lecoq de Boisbaudran in 1886 in France. Its name comes from the " - "Greek word dysprositos, which means hard to obtain.") - e['Ho'].description = ( + "Greek word dysprositos, which means hard to obtain." + ) + e["Ho"].description = ( "Relatively soft and malleable silvery-white metallic element, " "which is stable in dry air at room temperature. It oxidizes in " "moist air and at high temperatures. It belongs to the " @@ -2305,23 +4458,27 @@ def _descriptions(symbol): "Greek word Holmia which means Sweden. While all holmium compounds " "should be considered highly toxic, initial evidence seems to " "indicate that they do not pose much danger. The metal's dust " - "however, is a fire hazard.") - e['Er'].description = ( + "however, is a fire hazard." + ) + e["Er"].description = ( "Soft silvery metallic element which belongs to the lanthanoids. " "Six natural isotopes that are stable. Twelve artificial isotopes " "are known. Used in nuclear technology as a neutron absorber. It " "is being investigated for other possible uses. Discovered by Carl " - "G. Mosander in 1843.") - e['Tm'].description = ( + "G. Mosander in 1843." + ) + e["Tm"].description = ( "Soft grey metallic element that belongs to the lanthanoids. One " "natural isotope exists, Tm-169, and seventeen artificial isotopes " "have been produced. No known uses for the element. Discovered in " - "1879 by Per Theodor Cleve.") - e['Yb'].description = ( + "1879 by Per Theodor Cleve." + ) + e["Yb"].description = ( "Silvery metallic element of the lanthanoids. Seven natural " "isotopes and ten artificial isotopes are known. Used in certain " - "steels. Discovered by J.D.G. Marignac in 1878.") - e['Lu'].description = ( + "steels. Discovered by J.D.G. Marignac in 1878." + ) + e["Lu"].description = ( "Silvery-white rare-earth metal which is relatively stable in " "air. It happens to be the most expensive rare-earth metal. Its " "found with almost all rare-earth metals, but is very difficult to " @@ -2332,31 +4489,37 @@ def _descriptions(symbol): "3.3 years. The separation of lutetium from Ytterbium was " "described by Georges Urbain in 1907. It was discovered at " "approximately the same time by Carl Auer von Welsbach. The name " - "comes from the Greek word lutetia which means Paris.") - e['Hf'].description = ( + "comes from the Greek word lutetia which means Paris." + ) + e["Hf"].description = ( "Silvery lustrous metallic transition element. Used in tungsten " "alloys in filaments and electrodes, also acts as a neutron " "absorber. First reported by Urbain in 1911, existence was finally " - "established in 1923 by D. Coster, G.C. de Hevesy in 1923.") - e['Ta'].description = ( + "established in 1923 by D. Coster, G.C. de Hevesy in 1923." + ) + e["Ta"].description = ( "Heavy blue-grey metallic transition element. Ta-181 is a stable " "isotope, and Ta-180 is a radioactive isotope, with a half-life in " "excess of 10^7 years. Used in surgery as it is unreactive. Forms " "a passive oxide layer in air. Identified in 1802 by Ekeberg and " - "isolated in 1820 by Jons J. Berzelius.") - e['W'].description = ( + "isolated in 1820 by Jons J. Berzelius." + ) + e["W"].description = ( "White or grey metallic transition element,formerly called " "Wolfram. Forms a protective oxide in air and can be oxidized at " "high temperature. First isolated by Jose and Fausto de Elhuyer in " - "1783.") - e['Re'].description = ( + "1783." + ) + e["Re"].description = ( "Silvery-white metallic transition element. Obtained as a " "by-product of molybdenum refinement. Rhenium-molybdenum alloys " - "are superconducting.") - e['Os'].description = ( + "are superconducting." + ) + e["Os"].description = ( "Hard blue-white metallic transition element. Found with platinum " - "and used in some alloys with platinum and iridium.") - e['Ir'].description = ( + "and used in some alloys with platinum and iridium." + ) + e["Ir"].description = ( "Very hard and brittle, silvery metallic transition element. It " "has a yellowish cast to it. Salts of iridium are highly colored. " "It is the most corrosion resistant metal known, not attacked by " @@ -2369,8 +4532,9 @@ def _descriptions(symbol): "1803 by Smithson Tennant in England. The name comes from the " "Greek word iris, which means rainbow. Iridium metal is generally " "non-toxic due to its relative unreactivity, but iridium compounds " - "should be considered highly toxic.") - e['Pt'].description = ( + "should be considered highly toxic." + ) + e["Pt"].description = ( "Attractive greyish-white metal. When pure, it is malleable and " "ductile. Does not oxidize in air, insoluble in hydrochloric and " "nitric acid. Corroded by halogens, cyandies, sulphur and alkalis. " @@ -2385,8 +4549,9 @@ def _descriptions(symbol): "by Antonio de Ulloa in South America in 1735. The name comes from " "the Spanish word platina which means silver. Platinum metal is " "generally not a health concern due to its unreactivity, however " - "platinum compounds should be considered highly toxic.") - e['Au'].description = ( + "platinum compounds should be considered highly toxic." + ) + e["Au"].description = ( "Gold is gold colored. It is the most malleable and ductile metal " "known. There is only one stable isotope of gold, and five " "radioisotopes of gold, Au-195 being the most stable with a " @@ -2396,14 +4561,16 @@ def _descriptions(symbol): "exist as far back as 2600 BC. Gold comes from the Anglo-Saxon " "word gold. Its symbol, Au, comes from the Latin word aurum, which " "means gold. Gold is not particularly toxic, however it is known " - "to cause damage to the liver and kidneys in some.") - e['Hg'].description = ( + "to cause damage to the liver and kidneys in some." + ) + e["Hg"].description = ( "Heavy silvery liquid metallic element, belongs to the zinc " "group. Used in thermometers, barometers and other scientific " "apparatus. Less reactive than zinc and cadmium, does not displace " "hydrogen from acids. Forms a number of complexes and " - "organomercury compounds.") - e['Tl'].description = ( + "organomercury compounds." + ) + e["Tl"].description = ( "Pure, unreacted thallium appears silvery-white and exhibits a " "metallic lustre. Upon reacting with air, it begins to turn " "bluish-grey and looks like lead. It is very malleable, and can be " @@ -2414,82 +4581,95 @@ def _descriptions(symbol): "this gives it a use in infrared detectors. Discovered by Sir " "William Crookes via spectroscopy. Its name comes from the Greek " "word thallos, which means green twig. Thallium and its compounds " - "are toxic and can cause cancer.") - e['Pb'].description = ( + "are toxic and can cause cancer." + ) + e["Pb"].description = ( "Heavy dull grey ductile metallic element, belongs to group 14. " "Used in building construction, lead-place accumulators, bullets " "and shot, and is part of solder, pewter, bearing metals, type " - "metals and fusible alloys.") - e['Bi'].description = ( + "metals and fusible alloys." + ) + e["Bi"].description = ( "White crystalline metal with a pink tinge, belongs to group 15. " "Most diamagnetic of all metals and has the lowest thermal " "conductivity of all the elements except mercury. Lead-free " "bismuth compounds are used in cosmetics and medical procedures. " "Burns in the air and produces a blue flame. In 1753, C.G. Junine " - "first demonstrated that it was different from lead.") - e['Po'].description = ( + "first demonstrated that it was different from lead." + ) + e["Po"].description = ( "Rare radioactive metallic element, belongs to group 16 of the " "periodic table. Over 30 known isotopes exist, the most of all " "elements. Po-209 has a half-life of 103 years. Possible uses in " "heating spacecraft. Discovered by Marie Curie in 1898 in a sample " - "of pitchblende.") - e['At'].description = ( + "of pitchblende." + ) + e["At"].description = ( "Radioactive halogen element. Occurs naturally from uranium and " "thorium decay. At least 20 known isotopes. At-210, the most " "stable, has a half-life of 8.3 hours. Synthesized by nuclear " "bombardment in 1940 by D.R. Corson, K.R. MacKenzie and E. Segre " - "at the University of California.") - e['Rn'].description = ( + "at the University of California." + ) + e["Rn"].description = ( "Colorless radioactive gaseous element, belongs to the noble " "gases. Of the twenty known isotopes, the most stable is Rn-222 " "with a half-life of 3.8 days. Formed by the radioactive decay of " "Radium-226. Radon itself decays into Polonium. Used in " "radiotherapy. As a noble gas, it is effectively inert, though " "radon fluoride has been synthesized. First isolated in 1908 by " - "Ramsey and Gray.") - e['Fr'].description = ( + "Ramsey and Gray." + ) + e["Fr"].description = ( "Radioactive element, belongs to group 1 of the periodic table. " "Found in uranium and thorium ores. The 22 known isotopes are all " "radioactive, with the most stable being Fr-223. Its existence was " - "confirmed in 1939 by Marguerite Perey.") - e['Ra'].description = ( + "confirmed in 1939 by Marguerite Perey." + ) + e["Ra"].description = ( "Radioactive metallic transuranic element, belongs to group 2 of " "the periodic table. Most stable isotope, Ra-226 has a half-life " "of 1602 years, which decays into radon. Isolated from pitchblende " - "in 1898 Marie and Pierre Curie.") - e['Ac'].description = ( + "in 1898 Marie and Pierre Curie." + ) + e["Ac"].description = ( "Silvery radioactive metallic element, belongs to group 3 of the " "periodic table. The most stable isotope, Ac-227, has a half-life " "of 217 years. Ac-228 (half-life of 6.13 hours) also occurs in " "nature. There are 22 other artificial isotopes, all radioactive " "and having very short half-lives. Chemistry similar to " "lanthanumpy. Used as a source of alpha particles. Discovered by " - "A. Debierne in 1899.") - e['Th'].description = ( + "A. Debierne in 1899." + ) + e["Th"].description = ( "Grey radioactive metallic element. Belongs to actinoids. Found " "in monazite sand in Brazil, India and the US. Thorium-232 has a " "half-life of 1.39x10^10 years. Can be used as a nuclear fuel for " "breeder reactors. Thorium-232 captures slow Neutrons and breeds " - "uranium-233. Discovered by Jons J. Berzelius in 1829.") - e['Pa'].description = ( + "uranium-233. Discovered by Jons J. Berzelius in 1829." + ) + e["Pa"].description = ( "Radioactive metallic element, belongs to the actinoids. The most " "stable isotope, Pa-231 has a half-life of 2.43*10^4 years. At " "least 10 other radioactive isotopes are known. No practical " "applications are known. Discovered in 1917 by Lise Meitner and " - "Otto Hahn.") - e['U'].description = ( + "Otto Hahn." + ) + e["U"].description = ( "White radioactive metallic element belonging to the actinoids. " "Three natural isotopes, U-238, U-235 and U-234. Uranium-235 is " "used as the fuel for nuclear reactors and weapons. Discovered by " - "Martin H. Klaproth in 1789.") - e['Np'].description = ( + "Martin H. Klaproth in 1789." + ) + e["Np"].description = ( "Radioactive metallic transuranic element, belongs to the " "actinoids. Np-237, the most stable isotope, has a half-life of " "2.2*10^6 years and is a by product of nuclear reactors. The other " "known isotopes have mass numbers 229 through 236, and 238 through " "241. Np-236 has a half-life of 5*10^3 years. First produced by " - "Edwin M. McMillan and P.H. Abelson in 1940.") - e['Pu'].description = ( + "Edwin M. McMillan and P.H. Abelson in 1940." + ) + e["Pu"].description = ( "Dense silvery radioactive metallic transuranic element, belongs " "to the actinoids. Pu-244 is the most stable isotope with a " "half-life of 7.6*10^7 years. Thirteen isotopes are known. Pu-239 " @@ -2497,33 +4677,38 @@ def _descriptions(symbol): "neutrons and is hence important to nuclear weapons and reactors. " "Plutonium production is monitored down to the gram to prevent " "military misuse. First produced by Gleen T. Seaborg, Edwin M. " - "McMillan, J.W. Kennedy and A.C. Wahl in 1940.") - e['Am'].description = ( + "McMillan, J.W. Kennedy and A.C. Wahl in 1940." + ) + e["Am"].description = ( "Radioactive metallic transuranic element, belongs to the " "actinoids. Ten known isotopes. Am-243 is the most stable isotope, " "with a half-life of 7.95*10^3 years. Discovered by Glenn T. " "Seaborg and associates in 1945, it was obtained by bombarding " - "Uranium-238 with alpha particles.") - e['Cm'].description = ( + "Uranium-238 with alpha particles." + ) + e["Cm"].description = ( "Radioactive metallic transuranic element. Belongs to actinoid " "series. Nine known isotopes, Cm-247 has a half-life of 1.64*10^7 " "years. First identified by Glenn T. Seaborg and associates in " "1944, first produced by L.B. Werner and I. Perlman in 1947 by " - "bombarding americium-241 with Neutrons. Named for Marie Curie.") - e['Bk'].description = ( + "bombarding americium-241 with Neutrons. Named for Marie Curie." + ) + e["Bk"].description = ( "Radioactive metallic transuranic element. Belongs to actinoid " "series. Eight known isotopes, the most common Bk-247, has a " "half-life of 1.4*10^3 years. First produced by Glenn T. Seaborg " "and associates in 1949 by bombarding americium-241 with alpha " - "particles.") - e['Cf'].description = ( + "particles." + ) + e["Cf"].description = ( "Radioactive metallic transuranic element. Belongs to actinoid " "series. Cf-251 has a half life of about 700 years. Nine isotopes " "are known. Cf-252 is an intense Neutron source, which makes it an " "intense Neutron source and gives it a use in Neutron activation " "analysis and a possible use as a radiation source in medicine. " - "First produced by Glenn T. Seaborg and associates in 1950.") - e['Es'].description = ( + "First produced by Glenn T. Seaborg and associates in 1950." + ) + e["Es"].description = ( "Appearance is unknown, however it is most probably metallic and " "silver or gray in color. Radioactive metallic transuranic element " "belonging to the actinoids. Es-254 has the longest half-life of " @@ -2532,27 +4717,31 @@ def _descriptions(symbol): "explosion. In 1961 the first microgram quantities of Es-232 were " "separated. While einsteinium never exists naturally, if a " "sufficient amount was assembled, it would pose a radiation " - "hazard.") - e['Fm'].description = ( + "hazard." + ) + e["Fm"].description = ( "Radioactive metallic transuranic element, belongs to the " "actinoids. Ten known isotopes, most stable is Fm-257 with a " "half-life of 10 days. First identified by Albert Ghiorso and " "associates in the debris of the first hydrogen-bomb explosion in " - "1952.") - e['Md'].description = ( + "1952." + ) + e["Md"].description = ( "Radioactive metallic transuranic element. Belongs to the " "actinoid series. Only known isotope, Md-256 has a half-life of " "1.3 hours. First identified by Glenn T. Seaborg, Albert Ghiorso " "and associates in 1955. Alternative name Unnilunium has been " "proposed. Named after the 'inventor' of the periodic table, " - "Dmitri Mendeleev.") - e['No'].description = ( + "Dmitri Mendeleev." + ) + e["No"].description = ( "Radioactive metallic transuranic element, belongs to the " "actinoids. Seven known isotopes exist, the most stable being " "No-254 with a half-life of 255 seconds. First identified with " "certainty by Albert Ghiorso and Glenn T. Seaborg in 1966. " - "Unnilbium has been proposed as an alternative name.") - e['Lr'].description = ( + "Unnilbium has been proposed as an alternative name." + ) + e["Lr"].description = ( "Appearance unknown, however it is most likely silvery-white or " "grey and metallic. Lawrencium is a synthetic rare-earth metal. " "There are eight known radioisotopes, the most stable being Lr-262 " @@ -2563,37 +4752,44 @@ def _descriptions(symbol): "temporary IUPAC nomenclature, the origin of the name comes from " "Ernest O. Lawrence, the inventor of the cyclotron. If sufficient " "amounts of lawrencium were produced, it would pose a radiation " - "hazard.") - e['Rf'].description = ( + "hazard." + ) + e["Rf"].description = ( "Radioactive transactinide element. Expected to have similar " "chemical properties to those displayed by hafnium. Rf-260 was " "discovered by the Joint Nuclear Research Institute at Dubna " "(U.S.S.R.) in 1964. Researchers at Berkeley discovered Unq-257 " - "and Unq-258 in 1964.") - e['Db'].description = ( + "and Unq-258 in 1964." + ) + e["Db"].description = ( "Also known as Hahnium, Ha. Radioactive transactinide element. " "Half-life of 1.6s. Discovered in 1970 by Berkeley researchers. So " - "far, seven isotopes have been discovered.") - e['Sg'].description = ( + "far, seven isotopes have been discovered." + ) + e["Sg"].description = ( "Half-life of 0.9 +/- 0.2 s. Discovered by the Joint Institute " "for Nuclear Research at Dubna (U.S.S.R.) in June of 1974. Its " "existence was confirmed by the Lawrence Berkeley Laboratory and " - "Livermore National Laboratory in September of 1974.") - e['Bh'].description = ( + "Livermore National Laboratory in September of 1974." + ) + e["Bh"].description = ( "Radioactive transition metal. Half-life of approximately 1/500 " "s. Discovered by the Joint Institute for Nuclear Research at " "Dubna (U.S.S.R.) in 1976. Confirmed by West German physicists at " - "the Heavy Ion Research Laboratory at Darmstadt.") - e['Hs'].description = ( + "the Heavy Ion Research Laboratory at Darmstadt." + ) + e["Hs"].description = ( "Radioactive transition metal first synthesized in 1984 by a " "German research team led by Peter Armbruster and Gottfried " - "Muenzenberg at the Institute for Heavy Ion Research at Darmstadt.") - e['Mt'].description = ( + "Muenzenberg at the Institute for Heavy Ion Research at Darmstadt." + ) + e["Mt"].description = ( "Half-life of approximately 5 ms. The creation of this element " "demonstrated that fusion techniques could indeed be used to make " "new, heavy nuclei. Made and identified by physicists of the Heavy " "Ion Research Laboratory, Darmstadt, West Germany in 1982. Named " - "in honor of Lise Meitner, the Austrian physicist.") + "in honor of Lise Meitner, the Austrian physicist." + ) return e[symbol].description @@ -2612,7 +4808,8 @@ def sqlite_script(): >>> con.close() """ - sql = [""" + sql = [ + """ CREATE TABLE "period" ( "number" TINYINT NOT NULL PRIMARY KEY, "label" CHAR NOT NULL UNIQUE, @@ -2682,57 +4879,84 @@ def sqlite_script(): "energy" REAL NOT NULL, PRIMARY KEY ("element", "number") ); - """] + """ + ] for key, label in PERIODS.items(): - sql.append("""INSERT INTO "period" VALUES (%i, '%s', NULL);""" % ( - key, label)) + sql.append("""INSERT INTO "period" VALUES (%i, '%s', NULL);""" % (key, label)) for key, (label, descr) in GROUPS.items(): - sql.append("""INSERT INTO "group" VALUES (%i, '%s', '%s');""" % ( - key, label, descr)) + sql.append( + """INSERT INTO "group" VALUES (%i, '%s', '%s');""" % (key, label, descr) + ) for data in BLOCKS.items(): sql.append("""INSERT INTO "block" VALUES ('%s', '%s');""" % data) for series in sorted(SERIES): - sql.append("""INSERT INTO "series" VALUES (%i, '%s', '');""" % ( - series, SERIES[series])) + sql.append( + """INSERT INTO "series" VALUES (%i, '%s', '');""" % (series, SERIES[series]) + ) for ele in ELEMENTS: - sql.append(""" + sql.append( + """ INSERT INTO "element" VALUES (%i, '%s', '%s', %i, %i, '%s', %i, %.10f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.8f, '%s', '%s', '%s' - );""" % ( - ele.number, ele.symbol, ele.name, ele.period, ele.group, - ele.block, ele.series, ele.mass, ele.eleneg, - ele.covrad, ele.atmrad, ele.vdwrad, ele.tboil, ele.tmelt, - ele.density, ele.eleaffin, ele.eleconfig, ele.oxistates, - word_wrap( - ele.description.replace("'", "\'\'").replace("\"", "\"\""), - linelen=74, indent=0, joinstr="\n "))) + );""" + % ( + ele.number, + ele.symbol, + ele.name, + ele.period, + ele.group, + ele.block, + ele.series, + ele.mass, + ele.eleneg, + ele.covrad, + ele.atmrad, + ele.vdwrad, + ele.tboil, + ele.tmelt, + ele.density, + ele.eleaffin, + ele.eleconfig, + ele.oxistates, + word_wrap( + ele.description.replace("'", "''").replace('"', '""'), + linelen=74, + indent=0, + joinstr="\n ", + ), + ) + ) for ele in ELEMENTS: for iso in ele.isotopes.values(): sql.append( - """INSERT INTO "isotope" VALUES (%i, %i, %.10f, %.8f);""" % ( - ele.number, iso.massnumber, iso.mass, iso.abundance)) + """INSERT INTO "isotope" VALUES (%i, %i, %.10f, %.8f);""" + % (ele.number, iso.massnumber, iso.mass, iso.abundance) + ) for ele in ELEMENTS: for (shell, subshell), count in ele.eleconfig_dict.items(): sql.append( - """INSERT INTO "eleconfig" VALUES (%i, %i, '%s', %i);""" % ( - ele.number, shell, subshell, count)) + """INSERT INTO "eleconfig" VALUES (%i, %i, '%s', %i);""" + % (ele.number, shell, subshell, count) + ) for ele in ELEMENTS: for i, ionenergy in enumerate(ele.ionenergy): - sql.append("""INSERT INTO "ionenergy" VALUES (%i, %i, %.4f);""" % ( - ele.number, i + 1, ionenergy)) + sql.append( + """INSERT INTO "ionenergy" VALUES (%i, %i, %.4f);""" + % (ele.number, i + 1, ionenergy) + ) - return '\n'.join(sql).replace(" ", "") + return "\n".join(sql).replace(" ", "") def word_wrap(text, linelen=80, indent=0, joinstr="\n"): @@ -2757,6 +4981,7 @@ def word_wrap(text, linelen=80, indent=0, joinstr="\n"): if __name__ == "__main__": for ele in ELEMENTS: - print(repr(ele), '\n') + print(repr(ele), "\n") import doctest + doctest.testmod(verbose=False) diff --git a/cortix/support/phase.py b/cortix/support/phase.py index c1d51951..880a9830 100644 --- a/cortix/support/phase.py +++ b/cortix/support/phase.py @@ -8,7 +8,7 @@ # # Licensed under the University of Massachusetts Lowell LICENSE: # https://github.com/dpploy/cortix/blob/master/LICENSE.txt -''' +""" Phase *history* container. When you think of a phase value, think of that value at a specific point in time. This container holds the historic data of a phase; its species and quantities. This implementation treats access of time stamps within @@ -33,37 +33,34 @@ Sat Sep 5 01:26:53 EDT 2015 Cortix: a program for system-level modules coupling, execution, and analysis. -''' -#********************************************************************************* +""" + +# ********************************************************************************* import os import sys from copy import deepcopy import numpy as npy import pandas -from cortix.support.specie import Specie +from cortix.support.specie import Specie from cortix.support.quantity import Quantity -#********************************************************************************* +# ********************************************************************************* + class Phase: - ''' + """ Phase `history` container. A `Phase` consists of `Species` and `Quantities` varying with time. This container is meant to reproduce the basic idea of a material phase. - ''' - -#********************************************************************************* -# Construction -#********************************************************************************* - - def __init__(self, - time_stamp = None, - time_unit = None, - species = None, - quantities = None - ): - #TODO - ''' + """ + + # ********************************************************************************* + # Construction + # ********************************************************************************* + + def __init__(self, time_stamp=None, time_unit=None, species=None, quantities=None): + # TODO + """ Sometimes an empty Phase object is created by user code. This case needs adequate logic for None types. Note on usage: when passing quantities, do set the value argument explicitly @@ -72,16 +69,16 @@ def __init__(self, Maybe better to use a Quantity object and a Specie object with a Pandas Series history as a value to avoid the existance of a value in Quantity and a value in Phase that are not in sync. - ''' + """ if time_stamp is None: - time_stamp = 0.0 # default type is float + time_stamp = 0.0 # default type is float else: assert isinstance(time_stamp, float) self.__time_stamp = time_stamp if time_unit is None: - self.__time_unit = 's' # second + self.__time_unit = "s" # second else: assert isinstance(time_unit, str) self.__time_unit = time_unit @@ -109,17 +106,17 @@ def __init__(self, for specie in self.__species: names.append(specie.name) specie.massCC = 0.0 # clear these values - # todo: eliminate them from Specie in the future + # todo: eliminate them from Specie in the future if quantities is not None: for quant in self.__quantities: names.append(quant.name) - quant.value = 0.0 # clear these values - # todo: eliminate them from Quantity in the future + quant.value = 0.0 # clear these values + # todo: eliminate them from Quantity in the future # Table data phase without data type assigned; this is left to the user # Time stamps will always be float or int - self.__phase = pandas.DataFrame( index=[float(time_stamp)], columns=names ) + self.__phase = pandas.DataFrame(index=[float(time_stamp)], columns=names) if species is not None: for specie in species: @@ -128,25 +125,24 @@ def __init__(self, if quantities is not None: for quant in quantities: self.__phase.loc[time_stamp, quant.name] = quant.value - #self.__phase.fillna( 0.0, inplace=True ) # dtype defaults to float + # self.__phase.fillna( 0.0, inplace=True ) # dtype defaults to float return -#********************************************************************************* -# Public member functions -#********************************************************************************* + # ********************************************************************************* + # Public member functions + # ********************************************************************************* def has_time_stamp(self, try_time_stamp): - ''' + """ Checks to see if try_time_stamp exists in the phase history. Parameters ---------- try_time_stamp: - ''' - + """ - time_stamp = self.__get_time_stamp( try_time_stamp ) + time_stamp = self.__get_time_stamp(try_time_stamp) if time_stamp is not None: return True @@ -154,83 +150,92 @@ def has_time_stamp(self, try_time_stamp): return False def __get_time_unit(self): - ''' + """ Returns the time unit of the `Phase.` Returns ------- time_unit: str - ''' + """ return self.__time_unit - time_unit = property(__get_time_unit,None,None,None) + + time_unit = property(__get_time_unit, None, None, None) def GetTimeStamps(self): - ''' + """ Returns a list of all the time stamps in the phase history. Returns ------- timeStamps: list - ''' + """ return list(self.__phase.index) # return all time stamps + timeStamps = property(GetTimeStamps, None, None, None) def __get_time_stamps(self): - ''' + """ Get all time stamps in the index of the data frame. Returns ------- time_stamps: list - ''' + """ return list(self.__phase.index) # return all time stamps + time_stamps = property(__get_time_stamps, None, None, None) def GetSpecies(self): - ''' + """ Returns every single species in the phase history. Returns ------- species: list - ''' + """ for species in self.__species: - tmp = self.GetSpecie(species.name) # handy way to synchronize the whole list + tmp = self.GetSpecie( + species.name + ) # handy way to synchronize the whole list return self.__species + species = property(GetSpecies, None, None, None) def GetQuantities(self): - ''' + """ Returns the list of `Quantities`. The values in each `Quantity` are synchronized with the `Phase` data frame. Returns ------- quantities: list - ''' + """ for quant in self.__quantities: - tmp = self.GetQuantity(quant.name) # handy way to synchronize the whole list + tmp = self.GetQuantity( + quant.name + ) # handy way to synchronize the whole list return self.__quantities + quantities = property(GetQuantities, None, None, None) def GetActors(self): - ''' + """ Returns a list of all the actors in the phase history. Returns ------- list(self.__phase.colums): list - ''' + """ return list(self.__phase.columns) # return all names in order def GetSpecie(self, name): - ''' + """ Returns the species specified by name if it exists, or none if it doesn't. @@ -241,26 +246,28 @@ def GetSpecie(self, name): Returns ------- specie: str - ''' + """ for specie in self.__species: if specie.name == name: - time_stamp = self.__get_time_stamp( None ) # get latest time stamp - assert name in self.__phase.columns, 'name %r not in %r'% \ - (name,self.__phase.columns) + time_stamp = self.__get_time_stamp(None) # get latest time stamp + assert name in self.__phase.columns, "name %r not in %r" % ( + name, + self.__phase.columns, + ) specie.massCC = self.__phase.loc[time_stamp, name] return specie # return specie syncronized with the phase return None def SetSpecieId(self, name, val): - ''' + """ Sets the flag of a specie "name" equal to val. Parameters ---------- name: str val: int - ''' + """ for specie in self.__species: if specie.name == name: @@ -268,7 +275,7 @@ def SetSpecieId(self, name, val): return def GetQuantity(self, name): - ''' + """ Returns the quantity evaluated at the last time step of the phase history. This also updates the value of the quantity object. If the quantity name does not exist the return is None. @@ -276,20 +283,22 @@ def GetQuantity(self, name): Parameters ---------- name: str - ''' + """ for quant in self.__quantities: if quant.name == name: - time_stamp = self.__get_time_stamp( None ) # get latest time stamp - assert name in self.__phase.columns, 'name %r not in %r'%\ - (name,self.__phase.columns) + time_stamp = self.__get_time_stamp(None) # get latest time stamp + assert name in self.__phase.columns, "name %r not in %r" % ( + name, + self.__phase.columns, + ) quant.value = self.__phase.loc[time_stamp, name] return quant # return quantity syncronized with the phase return None def get_quantity(self, name, try_time_stamp=None): - ''' + """ New version. Get the quantity `name` at a point in time closest to `try_time_stamp` up to a tolerance. If no time stamp is passed, the @@ -305,20 +314,22 @@ def get_quantity(self, name, try_time_stamp=None): Returns ------- quant.value: float or int or other - ''' + """ - assert name in self.__phase.columns, 'name %r not in %r'%\ - (name,self.__phase.columns) + assert name in self.__phase.columns, "name %r not in %r" % ( + name, + self.__phase.columns, + ) - time_stamp = self.__get_time_stamp( try_time_stamp ) + time_stamp = self.__get_time_stamp(try_time_stamp) for quant in self.__quantities: if quant.name == name: - quant.value = self.__phase.loc[time_stamp, name] # labels' access mode + quant.value = self.__phase.loc[time_stamp, name] # labels' access mode return quant # return quantity syncronized with the phase def get_quantity_history(self, name): - ''' + """ Create a Quantity `name` history. This will create a fully qualified Quantity object and return to the caller. The function is typically needed for data output to a file through `pickle`. Since the value @@ -333,68 +344,74 @@ def get_quantity_history(self, name): Returns ------- quant_history: tuple(Quantity,str) - ''' + """ - assert name in self.__phase.columns, 'name %r not in %r'%\ - (name,self.__phase.columns) + assert name in self.__phase.columns, "name %r not in %r" % ( + name, + self.__phase.columns, + ) for quant in self.__quantities: if quant.name == name: quant_history = deepcopy(quant) - quant_history.value = self.__phase[name] # whole data frame index series - return (quant_history,self.__time_unit) # return tuple + quant_history.value = self.__phase[ + name + ] # whole data frame index series + return (quant_history, self.__time_unit) # return tuple def AddSpecie(self, new_specie): - ''' + """ Adds a new specie object to the phase history. See species.py for more details on the specie class. Parameters ---------- new_specie: obj - ''' + """ assert isinstance(new_specie, Specie) - assert new_specie.name not in list(self.__phase.columns), \ - 'new_specie: %r exists. Current names: %r' % \ - (new_specie, self.__phase.columns) + assert new_specie.name not in list(self.__phase.columns), ( + "new_specie: %r exists. Current names: %r" + % (new_specie, self.__phase.columns) + ) speciesFormulae = [specie.formula_name for specie in self.__species] assert new_specie.formula_name not in speciesFormulae self.__species.append(new_specie) newName = new_specie.name - col = pandas.DataFrame( index=list(self.__phase.index), columns=[newName] ) + col = pandas.DataFrame(index=list(self.__phase.index), columns=[newName]) tmp = self.__phase - df = tmp.join(col, how='outer') - self.__phase = df.fillna(0.0) # for species have float as default + df = tmp.join(col, how="outer") + self.__phase = df.fillna(0.0) # for species have float as default def AddQuantity(self, newQuant): - ''' + """ Adds a new quantity object to the dataframe. See quantity.py for more details on the quantity class. Parameters ---------- newQuant: object - ''' + """ assert isinstance(newQuant, Quantity) - assert newQuant.name not in list(self.__phase.columns), \ - 'quantity: %r exists. Current names: %r' % \ - (newQuant, self.__phase.columns) + assert newQuant.name not in list(self.__phase.columns), ( + "quantity: %r exists. Current names: %r" % (newQuant, self.__phase.columns) + ) quantFormalNames = [quant.formalName for quant in self.__quantities] assert newQuant.formalName not in quantFormalNames self.__quantities.append(newQuant) newName = newQuant.name # create a col with object data type; user must fill out column - col = pandas.DataFrame( index=list( self.__phase.index), columns=[newName], - dtype=object ) + col = pandas.DataFrame( + index=list(self.__phase.index), columns=[newName], dtype=object + ) tmp = self.__phase - df = tmp.join(col, how='outer') - #self.__phase = df.fillna(newQuant.value) + df = tmp.join(col, how="outer") + # self.__phase = df.fillna(newQuant.value) def AddRow(self, try_time_stamp, row_values): - ''' + """ Adds a row to the dataframe, with a timestamp of try_time_stamp and row values equal to row_values. Take care that the dimensions and order of the data matches up! @@ -403,31 +420,33 @@ def AddRow(self, try_time_stamp, row_values): ---------- try_time_stamp: float row_values: list - ''' + """ - assert try_time_stamp not in self.__phase.index, 'already used time_stamp: %r'%\ - (try_time_stamp) + assert try_time_stamp not in self.__phase.index, ( + "already used time_stamp: %r" % (try_time_stamp) + ) assert isinstance(row_values, list) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is None, 'already used time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is None, "already used time_stamp: %r" % (try_time_stamp) time_stamp = try_time_stamp assert len(row_values) == self.__phase.columns.size # create a row with object data type; users row_values data define data type - row = pandas.DataFrame( index=[time_stamp], - columns=list( self.__phase.columns ), dtype=object ) + row = pandas.DataFrame( + index=[time_stamp], columns=list(self.__phase.columns), dtype=object + ) - for (col,v) in zip(row.columns, row_values): - row.loc[time_stamp,col] = v + for col, v in zip(row.columns, row_values): + row.loc[time_stamp, col] = v frames = [self.__phase, row] self.__phase = pandas.concat(frames) return def GetRow(self, try_time_stamp=None): - ''' + """ Returns an entire row of the phase dataframe. A row is a series of values that are all at the same time stamp. @@ -438,14 +457,14 @@ def GetRow(self, try_time_stamp=None): Returns ------- list(self.__phase.loc[time_stamp, :]): list - ''' + """ - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) return list(self.__phase.loc[time_stamp, :]) def GetColumn(self, actor): - ''' + """ Returns an entire column of data. A column is the entire history of data associated with a specific actor. @@ -456,15 +475,17 @@ def GetColumn(self, actor): Returns ------- list(self.__phase.loc[:, actor]): list - ''' + """ assert isinstance(actor, str) - assert actor in self.__phase.columns, 'actor %r not in %r'% \ - (actor,self.__phase.columns) + assert actor in self.__phase.columns, "actor %r not in %r" % ( + actor, + self.__phase.columns, + ) return list(self.__phase.loc[:, actor]) def ScaleRow(self, try_time_stamp, value): - ''' + """ Multiplies all of the data in a row (except time stamp) by a scalar value. @@ -472,24 +493,24 @@ def ScaleRow(self, try_time_stamp, value): ---------- try_time_stamp: float value: float - ''' + """ assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) assert isinstance(value, int) or isinstance(value, float) self.__phase.loc[time_stamp, :] *= value return def ClearHistory(self, value=0.0): - ''' + """ Set species and quantities of history to a given value (default to zero value), all time stamps are preserved. Parameters ---------- value: float - ''' + """ assert isinstance(value, int) or isinstance(value, float) self.__phase.loc[:, :] = value @@ -497,7 +518,7 @@ def ClearHistory(self, value=0.0): return def ResetHistory(self, try_time_stamp=None, value=None): - ''' + """ Set species and quantities of history to a given value (default to zero value) only one time stamp is preserved (default to last time stamp). @@ -506,54 +527,59 @@ def ResetHistory(self, try_time_stamp=None, value=None): ---------- try_time_stamp: float value: float - ''' + """ if value is not None: - assert isinstance(value, int) or isinstance(value, float) or \ - isinstance(value, npy.ndarray) + assert ( + isinstance(value, int) + or isinstance(value, float) + or isinstance(value, npy.ndarray) + ) if try_time_stamp is not None: - assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) + assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) values = self.GetRow(time_stamp) # save values columns = list(self.__phase.columns) - assert len(columns) == len(values), 'FATAL: oops internal error.' + assert len(columns) == len(values), "FATAL: oops internal error." - self.__phase = pandas.DataFrame( index=[time_stamp], columns=columns ) - self.__phase.fillna( 0.0, inplace=True ) + self.__phase = pandas.DataFrame(index=[time_stamp], columns=columns) + self.__phase.fillna(0.0, inplace=True) if value is None: for v in values: idx = values.index(v) self.__phase.loc[time_stamp, columns[idx]] = v # restore values else: - self.__phase.loc[time_stamp, :] = value # set user-given value + self.__phase.loc[time_stamp, :] = value # set user-given value return def GetValue(self, actor, try_time_stamp=None): - ''' + """ Deprecated: use get_value() - ''' + """ assert isinstance(actor, str) - assert actor in self.__phase.columns, 'actor %r not in %r'% \ - (actor,self.__phase.columns) + assert actor in self.__phase.columns, "actor %r not in %r" % ( + actor, + self.__phase.columns, + ) if try_time_stamp is not None: - assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) + assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) return self.__phase.loc[time_stamp, actor] def get_value(self, actor, try_time_stamp=None): - ''' + """ Returns the value associated with a specified actor at a specified time stamp. @@ -565,22 +591,24 @@ def get_value(self, actor, try_time_stamp=None): Returns ------- self.__phase.loc[time_stamp, actor]: float - ''' + """ assert isinstance(actor, str) - assert actor in self.__phase.columns, 'actor %r not in %r'% \ - (actor,self.__phase.columns) + assert actor in self.__phase.columns, "actor %r not in %r" % ( + actor, + self.__phase.columns, + ) if try_time_stamp is not None: - assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) + assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) return self.__phase.loc[time_stamp, actor] def SetValue(self, actor, value, try_time_stamp=None): - ''' + """ For the record: old def SetValue(self, time_stamp, actor, value): Parameters @@ -588,33 +616,33 @@ def SetValue(self, actor, value, try_time_stamp=None): actor: str value: float try_time_stamp: float - ''' + """ assert isinstance(actor, str) assert actor in self.__phase.columns - #assert isinstance(value, int) or isinstance(value, float) or \ + # assert isinstance(value, int) or isinstance(value, float) or \ # isinstance(value, npy.ndarray) if try_time_stamp is not None: - assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) - - #print('*value =', value) - #print('*type(value) =', type(value)) - #print('*time_stamp =', time_stamp) - #print('*actor =', actor) - #print('*column values =', self.__phase[actor]) - #print('*df =', self.__phase) - #print('*df.dtypes =', self.__phase.dtypes) - #print('*df.shape =', self.__phase.shape) - #print('') - #if isinstance(value,npy.ndarray): + assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) + + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) + + # print('*value =', value) + # print('*type(value) =', type(value)) + # print('*time_stamp =', time_stamp) + # print('*actor =', actor) + # print('*column values =', self.__phase[actor]) + # print('*df =', self.__phase) + # print('*df.dtypes =', self.__phase.dtypes) + # print('*df.shape =', self.__phase.shape) + # print('') + # if isinstance(value,npy.ndarray): # self.__phase[actor] = self.__phase.astype({actor:type(value)}) - #print('*df.dtypes =', self.__phase.dtypes) - #print('') + # print('*df.dtypes =', self.__phase.dtypes) + # print('') # Note: user value could have a different type than other column values. # If there is a type change, this will not be checked; user has been advised. @@ -623,17 +651,17 @@ def SetValue(self, actor, value, try_time_stamp=None): return def set_value(self, actor, value, try_time_stamp=None): - ''' + """ New version. Discontinue using SetValue() - ''' + """ assert isinstance(actor, str) assert actor in self.__phase.columns if try_time_stamp is not None: - assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) + assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) # Note: user value could have a different type than other column values. # If there is a type change, this will not be checked; user has been advised. @@ -642,13 +670,13 @@ def set_value(self, actor, value, try_time_stamp=None): return def WriteHTML(self, fileName): - ''' + """ Convert the `Phase` container into an HTML file. Parameters --------- fileName: str - ''' + """ assert isinstance(fileName, str) tmp = pandas.DataFrame(self.__phase) @@ -663,31 +691,42 @@ def WriteHTML(self, fileName): elif col in quantityNames: idx = quantityNames.index(col) quant = self.__quantities[idx] - tmp.rename( columns={ col: col + '[' + quant.unit + ']'}, - inplace=True ) + tmp.rename(columns={col: col + "[" + quant.unit + "]"}, inplace=True) else: - assert False, 'oops fatal.' + assert False, "oops fatal." tmp.to_html(fileName) return def __str__(self): - s = '\n\t **Phase()**: \n\t time unit: %s\n\t *quantities*: %s\n\t *species*: %s\n\t *history* #time_stamp=%s\n\t *history end* @%s\n%s' - return s % (self.__time_unit,self.__quantities, self.__species, len(self.__phase.index), - self.__phase.index[-1], self.__phase.loc[self.__phase.index[-1], :]) + s = "\n\t **Phase()**: \n\t time unit: %s\n\t *quantities*: %s\n\t *species*: %s\n\t *history* #time_stamp=%s\n\t *history end* @%s\n%s" + return s % ( + self.__time_unit, + self.__quantities, + self.__species, + len(self.__phase.index), + self.__phase.index[-1], + self.__phase.loc[self.__phase.index[-1], :], + ) def __repr__(self): - s = '\n\t **Phase()**: \n\t time unit: %s\n\t *quantities*: %s\n\t *species*: %s\n\t *history* #time_stamp=%s\n\t *history end* @%s\n%s' - return s % (self.__time_unit,self.__quantities, self.__species, len(self.__phase.index), - self.__phase.index[-1], self.__phase.loc[self.__phase.index[-1], :]) - -#********************************************************************************* -# Private helper functions (internal use: __) -#********************************************************************************* + s = "\n\t **Phase()**: \n\t time unit: %s\n\t *quantities*: %s\n\t *species*: %s\n\t *history* #time_stamp=%s\n\t *history end* @%s\n%s" + return s % ( + self.__time_unit, + self.__quantities, + self.__species, + len(self.__phase.index), + self.__phase.index[-1], + self.__phase.loc[self.__phase.index[-1], :], + ) + + # ********************************************************************************* + # Private helper functions (internal use: __) + # ********************************************************************************* def __get_time_stamp(self, try_time_stamp=None): - ''' + """ Helper method for finding the closest time stamp to `try_time_stamp` in the phase history. The pandas index container used for storing float data type time stamps will return the nearest time stamp up to a @@ -703,7 +742,7 @@ def __get_time_stamp(self, try_time_stamp=None): ------- self.__phase.index[loc]: float or None Will return None if no time stamp within tolerance is found. - ''' + """ import numpy as np @@ -714,13 +753,15 @@ def __get_time_stamp(self, try_time_stamp=None): else: time_stamps = np.array(self.__phase.index) if time_stamps.size >= 2: - tol = 1.0e-3 * np.diff(time_stamps).mean() # 1e-3 * the mean delta t - try: # abs(index_value - try_time_stamp) <= tolerance - loc = self.__phase.index.get_loc( try_time_stamp, method='nearest', - tolerance=tol ) - except KeyError: # no value found withing tol + tol = 1.0e-3 * np.diff(time_stamps).mean() # 1e-3 * the mean delta t + try: # abs(index_value - try_time_stamp) <= tolerance + loc = self.__phase.index.get_loc( + try_time_stamp, method="nearest", tolerance=tol + ) + except KeyError: # no value found withing tol return None else: - return self.__phase.index[loc] + return self.__phase.index[loc] + -#======================= end class Phase ========================================= +# ======================= end class Phase ========================================= diff --git a/cortix/support/phase_new.py b/cortix/support/phase_new.py index da1edeee..4f683ab6 100644 --- a/cortix/support/phase_new.py +++ b/cortix/support/phase_new.py @@ -33,6 +33,7 @@ Cortix: a program for system-level modules coupling, execution, and analysis. """ + import os, io from copy import deepcopy import time @@ -42,30 +43,29 @@ import pandas import matplotlib -#needed? matplotlib.use('Agg', warn=False) + +# needed? matplotlib.use('Agg', warn=False) import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec from matplotlib.ticker import MultipleLocator from matplotlib.ticker import ScalarFormatter -from cortix.support.species import Species +from cortix.support.species import Species from cortix.support.quantity import Quantity + class PhaseNew: - ''' + """ Phase `history` container. A `Phase` consists of `Species` and `Quantities` varying with time. This container is meant to reproduce the basic idea of a material phase. - ''' - - def __init__(self, name = None, - time_stamp = None, - time_unit = None, - species = None, - quantities = None - ): - #TODO - ''' + """ + + def __init__( + self, name=None, time_stamp=None, time_unit=None, species=None, quantities=None + ): + # TODO + """ Sometimes an empty Phase object is created by user code. This case needs adequate logic for None types. Note on usage: when passing quantities, do set the value argument explicitly @@ -74,7 +74,7 @@ def __init__(self, name = None, Maybe better to use a Quantity object and a Species object with a Pandas Series history as a value to avoid the existance of a value in Quantity and a value in Phase that are not in sync. - ''' + """ if not name: self.name = self.__class__.__name__ @@ -82,13 +82,13 @@ def __init__(self, name = None, self.name = name if time_stamp is None: - time_stamp = 0.0 # default type is float + time_stamp = 0.0 # default type is float else: assert isinstance(time_stamp, float) self.__time_stamp = time_stamp if time_unit is None: - self.__time_unit = 's' # second + self.__time_unit = "s" # second else: assert isinstance(time_unit, str) self.__time_unit = time_unit @@ -125,12 +125,12 @@ def __init__(self, name = None, if quantities is not None: for quant in self.__quantities: names.append(quant.name) - quant.value = 0.0 # clear these values - # todo: eliminate them from Quantity in the future + quant.value = 0.0 # clear these values + # todo: eliminate them from Quantity in the future # Table data phase without data type assigned; this is left to the user # Time stamps will always be float - self.__df = pandas.DataFrame( index=[float(time_stamp)], columns=names ) + self.__df = pandas.DataFrame(index=[float(time_stamp)], columns=names) # This is meant to be the value of species concentration; a float type if species is not None: @@ -140,18 +140,18 @@ def __init__(self, name = None, if quantities is not None: for quant in quantities: self.__df.loc[time_stamp, quant.name] = quant.value - #self.__df.fillna( 0.0, inplace=True ) # dtype defaults to float + # self.__df.fillna( 0.0, inplace=True ) # dtype defaults to float return def has_time_stamp(self, try_time_stamp): - ''' + """ Checks to see if try_time_stamp exists in the phase history. Parameters ---------- try_time_stamp: - ''' + """ time_stamp = self.__get_time_stamp(try_time_stamp) @@ -161,79 +161,87 @@ def has_time_stamp(self, try_time_stamp): return False def __get_time_unit(self): - ''' + """ Returns the time unit of the `Phase.` Returns ------- time_unit: str - ''' + """ return self.__time_unit - time_unit = property(__get_time_unit,None,None,None) + + time_unit = property(__get_time_unit, None, None, None) def __get_time_stamps(self): - ''' + """ Get all time stamps in the index of the data frame. Returns ------- time_stamps: list - ''' + """ return list(self.__df.index) # return all time stamps + time_stamps = property(__get_time_stamps, None, None, None) def __get_species_list(self): - ''' + """ Returns every single species in the phase history. Returns ------- species: list - ''' + """ return self.__species + species = property(__get_species_list, None, None, None) def __get_quantities(self): - ''' + """ Returns the list of `Quantities`. The values in each `Quantity` are synchronized with the `Phase` data frame. Returns ------- quantities: list - ''' + """ for quant in self.__quantities: - tmp = self.get_quantity(quant.name) # handy way to synchronize the whole list + tmp = self.get_quantity( + quant.name + ) # handy way to synchronize the whole list return self.__quantities + quantities = property(__get_quantities, None, None, None) def __get_actors(self): - ''' + """ Returns a list of names of all the actors in the phase history. Returns ------- list(self.__df.colums): list - ''' + """ return list(self.__df.columns) # return all names in order + actors = property(__get_actors, None, None, None) def __get_df(self): - ''' + """ Die hard access. - ''' + """ return self.__df - df = property(__get_df,None,None,None) + + df = property(__get_df, None, None, None) def get_species(self, name): - ''' + """ Returns the species specified by name if it exists, or None if it doesn't. @@ -245,10 +253,12 @@ def get_species(self, name): ------- specie: str - ''' + """ - assert name in self.__df.columns, 'name %r not in %r'%\ - (name,self.__df.columns) + assert name in self.__df.columns, "name %r not in %r" % ( + name, + self.__df.columns, + ) if self.__species: for species in self.__species: @@ -257,7 +267,7 @@ def get_species(self, name): return None def get_species_concentration(self, name, try_time_stamp=None): - ''' + """ Returns the species concentration at `try_time_stamp`. Parameters @@ -270,11 +280,11 @@ def get_species_concentration(self, name, try_time_stamp=None): ------- concentration: float - ''' + """ return self.get_value(name, try_time_stamp) def set_species_id(self, name, val): - ''' + """ Sets the flag of a species "name" equal to val. Parameters @@ -282,9 +292,12 @@ def set_species_id(self, name, val): name: str val: int - ''' + """ - assert name in self.__df.columns, 'name %r not in %r'%(name,self.__df.columns) + assert name in self.__df.columns, "name %r not in %r" % ( + name, + self.__df.columns, + ) for species in self.__species: if species.name == name: @@ -292,7 +305,7 @@ def set_species_id(self, name, val): return def get_quantity(self, name): - ''' + """ Get the quantity with `name`. Parameters @@ -302,19 +315,21 @@ def get_quantity(self, name): Returns ------- Quantity or None - ''' + """ - assert name in self.__df.columns, 'name %r not in %r'%\ - (name,self.__df.columns) + assert name in self.__df.columns, "name %r not in %r" % ( + name, + self.__df.columns, + ) if self.__quantities: for quant in self.__quantities: if quant.name == name: - return quant + return quant return None def get_quantity_value(self, name, try_time_stamp=None): - ''' + """ Get the quantity value with given `name` at a point in time closest to `try_time_stamp` up to a tolerance. If no time stamp is passed, the value at the last time stamp is returned. @@ -329,21 +344,23 @@ def get_quantity_value(self, name, try_time_stamp=None): Returns ------- quant.value: type of Quantity.value - ''' + """ - assert name in self.__df.columns, 'name %r not in %r'%\ - (name,self.__df.columns) + assert name in self.__df.columns, "name %r not in %r" % ( + name, + self.__df.columns, + ) time_stamp = self.__get_time_stamp(try_time_stamp) if self.__quantities: for quant in self.__quantities: if quant.name == name: - quant.value = self.__df.loc[time_stamp, name] # labels' access mode + quant.value = self.__df.loc[time_stamp, name] # labels' access mode return quant # return quantity syncronized with the phase def get_quantity_history(self, name): - ''' + """ Creates a Quantity `name` history. This will create a fully qualified Quantity object and return to the caller. The function is typically needed for data output to a file through `pickle`. Since the value @@ -358,16 +375,20 @@ def get_quantity_history(self, name): Returns ------- quant_history: tuple(Quantity,str) or None - ''' + """ - assert name in self.__df.columns, 'name %r not in %r'%\ - (name,self.__df.columns) + assert name in self.__df.columns, "name %r not in %r" % ( + name, + self.__df.columns, + ) for quant in self.__quantities: if quant.name == name: quant_history = deepcopy(quant) - quant_history.value = deepcopy(self.__df[name]) # whole data frame index series - return (quant_history,self.__time_unit) # return tuple + quant_history.value = deepcopy( + self.__df[name] + ) # whole data frame index series + return (quant_history, self.__time_unit) # return tuple return None @@ -387,17 +408,18 @@ def add_single_species(self, new_species, discard_new_duplicate=False): assert isinstance(new_species, Species) if not discard_new_duplicate: - assert new_species.name not in list(self.__df.columns), \ - 'new_species: %r exists. Current names: %r' % \ - (new_species, self.__df.columns) + assert new_species.name not in list(self.__df.columns), ( + "new_species: %r exists. Current names: %r" + % (new_species, self.__df.columns) + ) - if self.__species is not None: # self.__species could be empty + if self.__species is not None: # self.__species could be empty species_formulae = [specie.formula_name for specie in self.__species] if not discard_new_duplicate: assert new_species.formula_name not in species_formulae - if self.__species is not None: # self.__species could be empty + if self.__species is not None: # self.__species could be empty species_names = [spc.name for spc in self.__species] if not discard_new_duplicate: @@ -409,42 +431,45 @@ def add_single_species(self, new_species, discard_new_duplicate=False): if self.__species is not None: self.__species.append(deepcopy(new_species)) else: - self.__species = [deepcopy(new_species)] # create a list here + self.__species = [deepcopy(new_species)] # create a list here new_name = new_species.name - col = pandas.DataFrame( index=list(self.__df.index), columns=[new_name] ) + col = pandas.DataFrame(index=list(self.__df.index), columns=[new_name]) tmp = self.__df - df = tmp.join(col, how='outer') - self.__df = df.fillna(0.0) # for species fill concentration with float as default + df = tmp.join(col, how="outer") + self.__df = df.fillna( + 0.0 + ) # for species fill concentration with float as default def add_quantity(self, new_quant): - ''' + """ Adds a new quantity object to the dataframe. See quantity.py for more details on the quantity class. Parameters ---------- new_quant: object - ''' + """ assert isinstance(new_quant, Quantity) - assert new_quant.name not in list(self.__df.columns), \ - 'quantity: %r exists. Current names: %r' % \ - (new_quant, self.__df.columns) + assert new_quant.name not in list(self.__df.columns), ( + "quantity: %r exists. Current names: %r" % (new_quant, self.__df.columns) + ) quant_formal_names = [quant.formal_name for quant in self.__quantities] assert new_quant.formal_name not in quant_formal_names self.__quantities.append(new_quant) new_name = new_quant.name # create a col with object data type; user must fill out column - col = pandas.DataFrame( index=list( self.__df.index), columns=[new_name], - dtype=object ) + col = pandas.DataFrame( + index=list(self.__df.index), columns=[new_name], dtype=object + ) tmp = self.__df - df = tmp.join(col, how='outer') - #self.__df = df.fillna(new_quant.value) + df = tmp.join(col, how="outer") + # self.__df = df.fillna(new_quant.value) def add_row(self, try_time_stamp, row_values): - ''' + """ Adds a row to the `DataFrame`, with a `timestamp` equal to `try_time_stamp` and row values equal to `row_values`. The length of `row_values` must match the number of columns in the data frame. @@ -454,30 +479,32 @@ def add_row(self, try_time_stamp, row_values): try_time_stamp: float row_values: list - ''' - assert try_time_stamp not in self.__df.index, 'already used time_stamp: %r'%\ - (try_time_stamp) + """ + assert try_time_stamp not in self.__df.index, "already used time_stamp: %r" % ( + try_time_stamp + ) assert isinstance(row_values, list) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is None, 'already used time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is None, "already used time_stamp: %r" % (try_time_stamp) time_stamp = try_time_stamp assert len(row_values) == self.__df.columns.size # create a row with object data type; users row_values data define data type - row = pandas.DataFrame( index=[time_stamp], - columns=list( self.__df.columns ), dtype=object ) + row = pandas.DataFrame( + index=[time_stamp], columns=list(self.__df.columns), dtype=object + ) - for (col,v) in zip(row.columns, row_values): - row.loc[time_stamp,col] = v + for col, v in zip(row.columns, row_values): + row.loc[time_stamp, col] = v frames = [self.__df, row] self.__df = pandas.concat(frames) return def get_row(self, try_time_stamp=None): - ''' + """ Returns an entire row of the phase dataframe. A row is a series of values that are all at the same time stamp. @@ -489,13 +516,13 @@ def get_row(self, try_time_stamp=None): ------- list(self.__df.loc[time_stamp, :]): list - ''' - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + """ + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) return list(self.__df.loc[time_stamp, :]) def get_column(self, actor): - ''' + """ Returns an entire column of data. A column is the entire history of data associated with a specific actor. @@ -507,14 +534,16 @@ def get_column(self, actor): ------- list(self.__df.loc[:, actor]): list - ''' + """ assert isinstance(actor, str) - assert actor in self.__df.columns, 'actor %r not in %r'% \ - (actor,self.__df.columns) + assert actor in self.__df.columns, "actor %r not in %r" % ( + actor, + self.__df.columns, + ) return list(self.__df.loc[:, actor]) def scale_row(self, try_time_stamp, value): - ''' + """ Multiplies all of the data in a row (except time stamp) by a scalar value. @@ -523,16 +552,16 @@ def scale_row(self, try_time_stamp, value): try_time_stamp: float value: float - ''' + """ assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) - #assert isinstance(value, int) or isinstance(value, float) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) + # assert isinstance(value, int) or isinstance(value, float) self.__df.loc[time_stamp, :] *= value return def ClearHistory(self, value=0.0): - ''' + """ Set species and quantities of history to a given value (default to zero value), all time stamps are preserved. @@ -540,14 +569,14 @@ def ClearHistory(self, value=0.0): ---------- value: float - ''' + """ assert isinstance(value, int) or isinstance(value, float) self.__df.loc[:, :] = value return def ResetHistory(self, try_time_stamp=None, value=None): - ''' + """ Set species and quantities of history to a given value (default to zero value) only one time stamp is preserved (default to last time stamp). @@ -557,36 +586,39 @@ def ResetHistory(self, try_time_stamp=None, value=None): try_time_stamp: float value: float - ''' + """ if value is not None: - assert isinstance(value, int) or isinstance(value, float) or \ - isinstance(value, np.ndarray) + assert ( + isinstance(value, int) + or isinstance(value, float) + or isinstance(value, np.ndarray) + ) if try_time_stamp is not None: - assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) + assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) values = self.GetRow(time_stamp) # save values columns = list(self.__df.columns) - assert len(columns) == len(values), 'FATAL: oops internal error.' + assert len(columns) == len(values), "FATAL: oops internal error." - self.__df = pandas.DataFrame( index=[time_stamp], columns=columns ) - self.__df.fillna( 0.0, inplace=True ) + self.__df = pandas.DataFrame(index=[time_stamp], columns=columns) + self.__df.fillna(0.0, inplace=True) if value is None: for v in values: idx = values.index(v) self.__df.loc[time_stamp, columns[idx]] = v # restore values else: - self.__df.loc[time_stamp, :] = value # set user-given value + self.__df.loc[time_stamp, :] = value # set user-given value return def get_value(self, actor, try_time_stamp=None): - ''' + """ Returns the value associated with a specified actor at a specified time stamp. @@ -600,23 +632,23 @@ def get_value(self, actor, try_time_stamp=None): ------- self.__df.loc[time_stamp, actor]: any - ''' + """ assert isinstance(actor, str) - assert actor in self.__df.columns, 'actor %r not in %r'% \ - (actor,self.__df.columns) + assert actor in self.__df.columns, "actor %r not in %r" % ( + actor, + self.__df.columns, + ) if try_time_stamp is not None: - assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) + assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) return self.__df.loc[time_stamp, actor] def set_value(self, actor, value, try_time_stamp=None): - ''' - - ''' + """ """ assert isinstance(actor, str) assert actor in self.__df.columns @@ -624,7 +656,7 @@ def set_value(self, actor, value, try_time_stamp=None): assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) time_stamp = self.__get_time_stamp(try_time_stamp) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) # Note: user value could have a different type than other column values. # If there is a type change, this will not be checked; user has been advised. @@ -633,14 +665,14 @@ def set_value(self, actor, value, try_time_stamp=None): return def write_html(self, fileName): - ''' + """ Convert the `Phase` container into an HTML file. Parameters --------- fileName: str - ''' + """ assert isinstance(fileName, str) tmp = pandas.DataFrame(self.__df) column_names = tmp.columns @@ -660,48 +692,56 @@ def write_html(self, fileName): elif col in quantity_names: idx = quantity_names.index(col) quant = self.__quantities[idx] - tmp.rename(columns={ col: col + '[' + quant.unit + ']'}, inplace=True) + tmp.rename(columns={col: col + "[" + quant.unit + "]"}, inplace=True) else: - assert False, 'oops fatal.' + assert False, "oops fatal." tmp.to_html(fileName) return def __str__(self): - s = '\n\t **Phase()**: name=%s;' + \ - '\n\t time unit: %s;' + \ - '\n\t *quantities*: %s;' + \ - '\n\t *species*: %s;' + \ - '\n\t *history* # time_stamps=%s;' + \ - '\n\t *history end* @%s;' + \ - '\n%s' - return s % (self.name, - self.__time_unit, - self.__quantities, - self.__species, - len(self.__df.index), - self.__df.index[-1], - self.__df.loc[self.__df.index[-1], :] ) + s = ( + "\n\t **Phase()**: name=%s;" + + "\n\t time unit: %s;" + + "\n\t *quantities*: %s;" + + "\n\t *species*: %s;" + + "\n\t *history* # time_stamps=%s;" + + "\n\t *history end* @%s;" + + "\n%s" + ) + return s % ( + self.name, + self.__time_unit, + self.__quantities, + self.__species, + len(self.__df.index), + self.__df.index[-1], + self.__df.loc[self.__df.index[-1], :], + ) def __repr__(self): - s = '\n\t **Phase()**: name=%s;' + \ - '\n\t time unit: %s;' + \ - '\n\t *quantities*: %s;' + \ - '\n\t *species*: %s;' + \ - '\n\t *history* # time_stamps=%s;' + \ - '\n\t *history end* @%s;' + \ - '\n%s' - return s % (self.name, - self.__time_unit, - self.__quantities, - self.__species, - len(self.__df.index), - self.__df.index[-1], - self.__df.loc[self.__df.index[-1], :] ) + s = ( + "\n\t **Phase()**: name=%s;" + + "\n\t time unit: %s;" + + "\n\t *quantities*: %s;" + + "\n\t *species*: %s;" + + "\n\t *history* # time_stamps=%s;" + + "\n\t *history end* @%s;" + + "\n%s" + ) + return s % ( + self.name, + self.__time_unit, + self.__quantities, + self.__species, + len(self.__df.index), + self.__df.index[-1], + self.__df.loc[self.__df.index[-1], :], + ) def __get_time_stamp(self, try_time_stamp=None): - ''' + """ Helper method for finding the closest time stamp to `try_time_stamp` in the phase history. The pandas index container used for storing float data type time stamps will return the nearest time stamp up to a @@ -718,7 +758,7 @@ def __get_time_stamp(self, try_time_stamp=None): self.__df.index[loc]: float or None Will return None if no time stamp within tolerance is found. - ''' + """ import numpy as np tol = 1.0e-3 @@ -728,31 +768,41 @@ def __get_time_stamp(self, try_time_stamp=None): else: time_stamps = np.array(self.__df.index) if time_stamps.size >= 2: - tol = 1.0e-3 * np.diff(time_stamps).mean() # 1e-3 * the mean delta t + tol = 1.0e-3 * np.diff(time_stamps).mean() # 1e-3 * the mean delta t # deprecated (no exception thrown) - #try: # abs(index_value - try_time_stamp) <= tolerance + # try: # abs(index_value - try_time_stamp) <= tolerance # loc = self.__df.index.get_loc(try_time_stamp, method='nearest', tolerance=tol) - #except KeyError: # no value found within tol + # except KeyError: # no value found within tol # return None - #else: + # else: # return self.__df.index[loc] # abs(index_value - try_time_stamp) <= tolerance - loc = self.__df.index.get_indexer([try_time_stamp], method='nearest', tolerance=tol)[0] + loc = self.__df.index.get_indexer( + [try_time_stamp], method="nearest", tolerance=tol + )[0] if loc == -1: return None else: - return self.__df.index[loc] - - def plot_species(self, name, scaling=[1.0, 1.0] , title=None, xlabel='Time [s]', - ylabel='y', legend=None, filename_tag=None, figsize=[6,5], - dpi=100 ): - + return self.__df.index[loc] + + def plot_species( + self, + name, + scaling=[1.0, 1.0], + title=None, + xlabel="Time [s]", + ylabel="y", + legend=None, + filename_tag=None, + figsize=[6, 5], + dpi=100, + ): if legend is not None: assert isinstance(legend, str) - fig,ax=plt.subplots(1, figsize=figsize) + fig, ax = plt.subplots(1, figsize=figsize) x = np.array([t for t in self.__df.index]) x *= float(scaling[0]) @@ -760,43 +810,52 @@ def plot_species(self, name, scaling=[1.0, 1.0] , title=None, xlabel='Time [s]', y = np.array(self.get_column(name), dtype=np.float64) y *= float(scaling[1]) - yformatter = ScalarFormatter(useMathText=True,useOffset=True) + yformatter = ScalarFormatter(useMathText=True, useOffset=True) yformatter.set_powerlimits((15, 5)) ax.yaxis.set_major_formatter(yformatter) if legend: - ax.plot(x, y, 'b-', label=legend) + ax.plot(x, y, "b-", label=legend) else: - ax.plot(x, y, 'b-') + ax.plot(x, y, "b-") - ax.set_xlabel(r''+xlabel,fontsize=16) - ax.set_ylabel(r''+ylabel,fontsize=16,color='black') - ax.tick_params(axis='y',labelsize=14) - ax.tick_params(axis='x',labelsize=14) + ax.set_xlabel(r"" + xlabel, fontsize=16) + ax.set_ylabel(r"" + ylabel, fontsize=16, color="black") + ax.tick_params(axis="y", labelsize=14) + ax.tick_params(axis="x", labelsize=14) if legend: - ax.legend(loc='best',fontsize=12) + ax.legend(loc="best", fontsize=12) if title: ax.set_title(title) elif self.get_species(name).info: - ax.set_title(self.get_species(name).info,fontsize=14) + ax.set_title(self.get_species(name).info, fontsize=14) ax.grid(True) - fig_name = name+'-'+self.name+'-phase-plot-' + fig_name = name + "-" + self.name + "-phase-plot-" if filename_tag: fig_name += filename_tag - fig.savefig(fig_name+'.png', dpi=dpi, format='png') + fig.savefig(fig_name + ".png", dpi=dpi, format="png") plt.close(fig) return - def plot(self, actors=None, name='phase-plot-null-name', - var_unit=None, - legend=None, nrows=2, ncols=2, figsize=[6,5], show=False, dpi=200): + def plot( + self, + actors=None, + name="phase-plot-null-name", + var_unit=None, + legend=None, + nrows=2, + ncols=2, + figsize=[6, 5], + show=False, + dpi=200, + ): """Plot assistant for a phase container. Make plots of all actors. @@ -841,27 +900,30 @@ def plot(self, actors=None, name='phase-plot-null-name', i_dash = 0 for i_var in range(num_var): - col_name = actors[i_var] # if multiple of nrows*ncols start new dashboard - if i_var % (nrows*ncols) == 0: - + if i_var % (nrows * ncols) == 0: if i_var != 0: # flush any current figure - fig_name = lead_name+'-'+self.name+'-phase-plot-' + \ - str(i_dash).zfill(2) - fig.savefig(fig_name+'.png', dpi=dpi, format='png') - #plt.close(fig_num) - - #pickle.dump( fig, open(fig_name+'.pickle','wb') ) + fig_name = ( + lead_name + + "-" + + self.name + + "-phase-plot-" + + str(i_dash).zfill(2) + ) + fig.savefig(fig_name + ".png", dpi=dpi, format="png") + # plt.close(fig_num) + + # pickle.dump( fig, open(fig_name+'.pickle','wb') ) i_dash += 1 - fig_num = str(np.random.random()) + '.' + str(i_dash) + fig_num = str(np.random.random()) + "." + str(i_dash) fig = plt.figure(num=fig_num, figsize=figsize) gs = gridspec.GridSpec(nrows, ncols) - #gs.update(left=0.08, right=0.98, wspace=0.4, hspace=0.4) + # gs.update(left=0.08, right=0.98, wspace=0.4, hspace=0.4) gs.update(left=0.13, right=0.98, wspace=0.4, hspace=0.4) axlst = list() @@ -879,8 +941,8 @@ def plot(self, actors=None, name='phase-plot-null-name', axes = np.array(axlst) - text = today + ': Cortix.Phase.Plot' - fig.text(.5, .95, text, horizontalalignment='center', fontsize=14) + text = today + ": Cortix.Phase.Plot" + fig.text(0.5, 0.95, text, horizontalalignment="center", fontsize=14) axs = axes.flat @@ -890,14 +952,14 @@ def plot(self, actors=None, name='phase-plot-null-name', # start a new dashboard if var_unit is None: - var_unit = 'g/L' + var_unit = "g/L" ax = axs[axId] axId += 1 species = self.get_species(col_name) if species: - #latex_name = species.latex_name # not working + # latex_name = species.latex_name # not working latex_name = species.name var_unit = var_unit info_str = species.info @@ -917,10 +979,18 @@ def plot(self, actors=None, name='phase-plot-null-name', assert self.__quantities[i_var].name == self.__df.columns[i_var] elif quantity: if i_var > len(self.__species): - assert self.__quantities[i_var].name == self.__df.columns[i_var], \ - 'ivar=%r; __quant[i]=%r; __df.col[i]=%r; __quant=%r; __df.col=%r'%(i_var, self.__quantities[i_var].name, self.__df.columns[i_var], self.__quantities, self.__df.columns) - - ''' + assert self.__quantities[i_var].name == self.__df.columns[i_var], ( + "ivar=%r; __quant[i]=%r; __df.col[i]=%r; __quant=%r; __df.col=%r" + % ( + i_var, + self.__quantities[i_var].name, + self.__df.columns[i_var], + self.__quantities, + self.__df.columns, + ) + ) + + """ if varUnit == 'gram': varUnit = 'g' if varUnit == 'gram/min': @@ -933,33 +1003,41 @@ def plot(self, actors=None, name='phase-plot-null-name', varUnit = 'g/L' if varUnit == 'sec': varUnit = 's' - ''' + """ varLegend = legend - varScale = 'linear-linear' + varScale = "linear-linear" - assert varScale == 'log' or varScale == 'linear' or varScale == 'log-linear' \ - or varScale == 'linear-log' or varScale == 'linear-linear' or \ - varScale == 'log-log' + assert ( + varScale == "log" + or varScale == "linear" + or varScale == "log-linear" + or varScale == "linear-log" + or varScale == "linear-linear" + or varScale == "log-log" + ) - time_unit = 's' + time_unit = "s" - if time_unit == 'minute': - time_unit = 'min' + if time_unit == "minute": + time_unit = "min" - x = np.array( [i for i in self.__df.index] ) + x = np.array([i for i in self.__df.index]) - if (varScale == 'linear' or varScale == 'linear-linear' or \ - varScale == 'linear-log') and x.max() >= 60.0: + if ( + varScale == "linear" + or varScale == "linear-linear" + or varScale == "linear-log" + ) and x.max() >= 60.0: x /= 60.0 - if time_unit == 'min': - time_unit = 'h' - if time_unit == 'second' or time_unit=='s': - time_unit = 'min' + if time_unit == "min": + time_unit = "h" + if time_unit == "second" or time_unit == "s": + time_unit = "min" - y = np.array( self.__df[col_name] ) # convert to numpy ndarray + y = np.array(self.__df[col_name]) # convert to numpy ndarray - ''' + """ if (y.max() >= 1e3 or y.min() <= -1e3) and varScale != 'linear-log' and \ varScale != 'log-log' and varScale != 'log': y /= 1e3 @@ -1101,13 +1179,13 @@ def plot(self, actors=None, name='phase-plot-null-name', varUnit = 'ms' if varUnit == 'm/s': varUnit = 'mm/s' - ''' + """ - ax.set_xlabel('Time [' + time_unit + ']', fontsize=12) - #ax.set_ylabel('$'+latex_name+'$' + ' [' + var_unit + ']', fontsize=12) - ax.set_ylabel(latex_name+' [' + var_unit + ']', fontsize=12) + ax.set_xlabel("Time [" + time_unit + "]", fontsize=12) + # ax.set_ylabel('$'+latex_name+'$' + ' [' + var_unit + ']', fontsize=12) + ax.set_ylabel(latex_name + " [" + var_unit + "]", fontsize=12) - ''' + """ ymax = y.max() dy = ymax * .1 ymax += dy @@ -1119,7 +1197,7 @@ def plot(self, actors=None, name='phase-plot-null-name', ymax = 1.0 ax.set_ylim(ymin, ymax) - ''' + """ if ncols >= 4: for l in ax.get_xticklabels(): @@ -1130,16 +1208,16 @@ def plot(self, actors=None, name='phase-plot-null-name', for l in ax.get_yticklabels(): l.set_fontsize(10) - if time_unit == 'h' and x.max() - x.min() <= 5.0: + if time_unit == "h" and x.max() - x.min() <= 5.0: majorLocator = MultipleLocator(1.0) minorLocator = MultipleLocator(0.5) ax.xaxis.set_major_locator(majorLocator) ax.xaxis.set_minor_locator(minorLocator) - if varScale == 'log' or varScale == 'log-log': - ax.set_xscale('log') - ax.set_yscale('log') + if varScale == "log" or varScale == "log-log": + ax.set_xscale("log") + ax.set_yscale("log") positiveX = x > 0.0 x = np.extract(positiveX, x) y = np.extract(positiveX, y) @@ -1149,7 +1227,7 @@ def plot(self, actors=None, name='phase-plot-null-name', if y.size > 0: if y.min() > 0.0 and y.max() > y.min(): ymax = y.max() - dy = ymax * .1 + dy = ymax * 0.1 ymax += dy ymin = y.min() ymin -= dy @@ -1161,22 +1239,22 @@ def plot(self, actors=None, name='phase-plot-null-name', else: ax.set_ylim(1.0, 10.0) - if varScale == 'log-linear': - ax.set_xscale('log') - positiveX = x > 0.0 # True if > 0.0 + if varScale == "log-linear": + ax.set_xscale("log") + positiveX = x > 0.0 # True if > 0.0 x = np.extract(positiveX, x) y = np.extract(positiveX, y) - if varScale == 'linear-log': - ax.set_yscale('log') - positiveY = y > 0.0 # True if > 0.0 + if varScale == "linear-log": + ax.set_yscale("log") + positiveY = y > 0.0 # True if > 0.0 x = np.extract(positiveY, x) y = np.extract(positiveY, y) - #assert x.size == y.size, 'size error; stop.' + # assert x.size == y.size, 'size error; stop.' if y.size > 0: if y.min() > 0.0 and y.max() > y.min(): ymax = y.max() - dy = ymax * .1 + dy = ymax * 0.1 ymax += dy ymin = y.min() ymin -= dy @@ -1191,43 +1269,58 @@ def plot(self, actors=None, name='phase-plot-null-name', # ................... # make the plot here - yformatter = ScalarFormatter(useMathText=True,useOffset=True) + yformatter = ScalarFormatter(useMathText=True, useOffset=True) yformatter.set_powerlimits((15, 5)) ax.yaxis.set_major_formatter(yformatter) if varLegend: - - ax.plot(x, y, 's-', color='black', linewidth=0.5, markersize=2, - markeredgecolor='black', label=varLegend) + ax.plot( + x, + y, + "s-", + color="black", + linewidth=0.5, + markersize=2, + markeredgecolor="black", + label=varLegend, + ) else: - - ax.plot(x, y, 's-', color='black', linewidth=0.5, markersize=2, - markeredgecolor='black') + ax.plot( + x, + y, + "s-", + color="black", + linewidth=0.5, + markersize=2, + markeredgecolor="black", + ) # ................... ax.set_title(info_str, fontsize=14) if varLegend: - ax.legend(loc='best', prop={'size': 7}) + ax.legend(loc="best", prop={"size": 7}) ax.grid() # end of: for i_var in range(num_var): - fig_name = lead_name+'-'+self.name+'-phase-plot-'+str(i_dash).zfill(2) - fig.savefig(fig_name+'.png', dpi=dpi, format='png') + fig_name = lead_name + "-" + self.name + "-phase-plot-" + str(i_dash).zfill(2) + fig.savefig(fig_name + ".png", dpi=dpi, format="png") - #plt.close(fig_num) + # plt.close(fig_num) if show: plt.show() - #pickle.dump( fig, open(fig_name+'.pickle','wb') ) + # pickle.dump( fig, open(fig_name+'.pickle','wb') ) return -if __name__ == '__main__': - tbp_org = Species(name='TBP', formula_name='(C4H9O)_3PO(o)', - atoms=['12*C','27*H','4*O','P'] ) - quant = Quantity( name='volume' ) - phase = PhaseNew(name='solvent',species=[tbp_org],quantities=[quant]) + +if __name__ == "__main__": + tbp_org = Species( + name="TBP", formula_name="(C4H9O)_3PO(o)", atoms=["12*C", "27*H", "4*O", "P"] + ) + quant = Quantity(name="volume") + phase = PhaseNew(name="solvent", species=[tbp_org], quantities=[quant]) print(phase) diff --git a/cortix/support/phase_newest.py b/cortix/support/phase_newest.py index 92fa7811..920d19bd 100644 --- a/cortix/support/phase_newest.py +++ b/cortix/support/phase_newest.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # This file is part of the Cortix toolkit environment # https://cortix.org -''' +""" Phase *history* container. When you think of a phase value, think of that value at a specific point in time. This container holds the historic data of a phase; its species and quantities. This implementation treats access of time stamps within @@ -28,7 +28,8 @@ Sat Sep 5 01:26:53 EDT 2015 Cortix: a program for system-level modules coupling, execution, and analysis. -''' +""" + import os, io from copy import deepcopy import time @@ -37,30 +38,29 @@ import numpy as np import matplotlib -matplotlib.use('Agg', warn=False) + +matplotlib.use("Agg", warn=False) import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec from matplotlib.ticker import MultipleLocator from matplotlib.ticker import ScalarFormatter -from cortix.support.species import Species +from cortix.support.species import Species from cortix.support.quantity import Quantity + class PhaseNewest: - ''' + """ Phase `history` container. A `Phase` consists of `Species` and `Quantities` varying with time. This container is meant to reproduce the basic idea of a material phase. - ''' - - def __init__(self, name = None, - time_stamp = None, - time_unit = None, - species = None, - quantities = None - ): - #TODO - ''' + """ + + def __init__( + self, name=None, time_stamp=None, time_unit=None, species=None, quantities=None + ): + # TODO + """ Sometimes an empty Phase object is created by user code. This case needs adequate logic for None types. Note on usage: when passing quantities, do set the value argument explicitly @@ -69,7 +69,7 @@ def __init__(self, name = None, Maybe better to use a Quantity object and a Species object with a Pandas Series history as a value to avoid the existance of a value in Quantity and a value in Phase that are not in sync. - ''' + """ if not name: self.__name = self.__class__.__name__ @@ -77,12 +77,12 @@ def __init__(self, name = None, self.__name = name if time_stamp is None: - time_stamp = 0.0 # default type is float + time_stamp = 0.0 # default type is float else: assert isinstance(time_stamp, float) if time_unit is None: - self.__time_unit = 's' # second + self.__time_unit = "s" # second else: assert isinstance(time_unit, str) self.__time_unit = time_unit @@ -104,11 +104,11 @@ def __init__(self, name = None, # A new object held by a Phase() object self.__quantities = deepcopy(quantities) - self.__names = list() # ordered list of names + self.__names = list() # ordered list of names # Table data phase without data type assigned; this is left to the user # Time stamps will always be float - #self.__df = pandas.DataFrame( index=[float(time_stamp)], columns=names ) + # self.__df = pandas.DataFrame( index=[float(time_stamp)], columns=names ) self.__df = dict(list()) if species is not None: @@ -122,10 +122,10 @@ def __init__(self, name = None, name = quant.name assert name not in self.__names self.__names.append(name) - quant.value = 0.0 # clear these values - # todo: eliminate them from Quantity in the future + quant.value = 0.0 # clear these values + # todo: eliminate them from Quantity in the future - self.__df['time-stamps'] = [time_stamp] + self.__df["time-stamps"] = [time_stamp] # This is meant to be the value of species concentration; a float type if species is not None: @@ -139,15 +139,15 @@ def __init__(self, name = None, return def has_time_stamp(self, try_time_stamp): - ''' + """ Checks to see if try_time_stamp exists in the phase history. Parameters ---------- try_time_stamp: - ''' + """ - time_stamp = self.__get_time_stamp( try_time_stamp ) + time_stamp = self.__get_time_stamp(try_time_stamp) if time_stamp is not None: return True @@ -155,59 +155,65 @@ def has_time_stamp(self, try_time_stamp): return False def __get_time_unit(self): - ''' + """ Returns the time unit of the `Phase.` Returns ------- time_unit: str - ''' + """ return self.__time_unit - time_unit = property(__get_time_unit,None,None,None) + + time_unit = property(__get_time_unit, None, None, None) def __get_time_stamps(self): - ''' + """ Get all time stamps in the index of the data frame. Returns ------- time_stamps: list - ''' + """ + + # return list(self.__df.index) # return all time stamps + return self.__df["time-stamps"] # return all time stamps - #return list(self.__df.index) # return all time stamps - return self.__df.['time-stamps'] # return all time stamps time_stamps = property(__get_time_stamps, None, None, None) def __get_species_list(self): - ''' + """ Returns every single species in the phase history. Returns ------- species: list - ''' + """ return self.__species + species = property(__get_species_list, None, None, None) def GetQuantities(self): - ''' + """ Returns the list of `Quantities`. The values in each `Quantity` are synchronized with the `Phase` data frame. Returns ------- quantities: list - ''' + """ for quant in self.__quantities: - tmp = self.GetQuantity(quant.name) # handy way to synchronize the whole list + tmp = self.GetQuantity( + quant.name + ) # handy way to synchronize the whole list return self.__quantities + quantities = property(GetQuantities, None, None, None) def __get_actors(self): - ''' + """ Returns a list of names of all the actors in the phase history. Returns @@ -215,21 +221,23 @@ def __get_actors(self): #list(self.__df.colums): list names: list - ''' + """ + + # return list(self.__df.columns) # return all names in order + return self.__names # return all names in order - #return list(self.__df.columns) # return all names in order - return self.__names # return all names in order actors = property(__get_actors, None, None, None) def __get_df(self): - ''' + """ Die hard access. - ''' + """ return self.__df - df = property(__get_df,None,None,None) + + df = property(__get_df, None, None, None) def get_species(self, name): - ''' + """ Returns the species specified by name if it exists, or None if it doesn't. @@ -241,14 +249,14 @@ def get_species(self, name): ------- specie: str - ''' + """ for species in self.__species: if species.name == name: return species return None def get_species_concentration(self, name, try_time_stamp=None): - ''' + """ Returns the species concentration at `try_time_stamp`. Parameters @@ -261,11 +269,11 @@ def get_species_concentration(self, name, try_time_stamp=None): ------- concentration: float - ''' - return self.get_value(name,try_time_stamp) + """ + return self.get_value(name, try_time_stamp) def set_species_id(self, name, val): - ''' + """ Sets the flag of a species "name" equal to val. Parameters @@ -273,14 +281,14 @@ def set_species_id(self, name, val): name: str val: int - ''' + """ for species in self.__species: if species.name == name: species.flag = val return def get_quantity(self, name, try_time_stamp=None): - ''' + """ Get the quantity `name` at a point in time closest to `try_time_stamp` up to a tolerance. If no time stamp is passed, the value at the last time stamp is returned. @@ -295,20 +303,22 @@ def get_quantity(self, name, try_time_stamp=None): Returns ------- quant.value: float or int or other - ''' + """ - assert name in self.__df.columns, 'name %r not in %r'%\ - (name,self.__df.columns) + assert name in self.__df.columns, "name %r not in %r" % ( + name, + self.__df.columns, + ) - time_stamp = self.__get_time_stamp( try_time_stamp ) + time_stamp = self.__get_time_stamp(try_time_stamp) for quant in self.__quantities: if quant.name == name: - quant.value = self.__df.loc[time_stamp, name] # labels' access mode + quant.value = self.__df.loc[time_stamp, name] # labels' access mode return quant # return quantity syncronized with the phase def get_quantity_history(self, name): - ''' + """ Create a Quantity `name` history. This will create a fully qualified Quantity object and return to the caller. The function is typically needed for data output to a file through `pickle`. Since the value @@ -323,68 +333,74 @@ def get_quantity_history(self, name): Returns ------- quant_history: tuple(Quantity,str) - ''' + """ - assert name in self.__df.columns, 'name %r not in %r'%\ - (name,self.__df.columns) + assert name in self.__df.columns, "name %r not in %r" % ( + name, + self.__df.columns, + ) for quant in self.__quantities: if quant.name == name: quant_history = deepcopy(quant) - quant_history.value = deepcopy(self.__df[name]) # whole data frame index series - return (quant_history,self.__time_unit) # return tuple + quant_history.value = deepcopy( + self.__df[name] + ) # whole data frame index series + return (quant_history, self.__time_unit) # return tuple def add_single_species(self, new_species): - ''' + """ Adds a new specie object to the phase history. See species.py for more details on the Species class. Parameters ---------- new_species: obj - ''' + """ assert isinstance(new_species, Species) - assert new_species.name not in list(self.__df.columns), \ - 'new_species: %r exists. Current names: %r' % \ - (new_species, self.__df.columns) + assert new_species.name not in list(self.__df.columns), ( + "new_species: %r exists. Current names: %r" + % (new_species, self.__df.columns) + ) species_formulae = [specie.formula_name for specie in self.__species] assert new_species.formula_name not in species_formulae self.__species.append(new_species) new_name = new_species.name - col = pandas.DataFrame( index=list(self.__df.index), columns=[new_name] ) + col = pandas.DataFrame(index=list(self.__df.index), columns=[new_name]) tmp = self.__df - df = tmp.join(col, how='outer') - self.__df = df.fillna(0.0) # for species have float as default + df = tmp.join(col, how="outer") + self.__df = df.fillna(0.0) # for species have float as default def add_quantity(self, new_quant): - ''' + """ Adds a new quantity object to the dataframe. See quantity.py for more details on the quantity class. Parameters ---------- new_quant: object - ''' + """ assert isinstance(new_quant, Quantity) - assert new_quant.name not in list(self.__df.columns), \ - 'quantity: %r exists. Current names: %r' % \ - (new_quant, self.__df.columns) + assert new_quant.name not in list(self.__df.columns), ( + "quantity: %r exists. Current names: %r" % (new_quant, self.__df.columns) + ) quant_formal_names = [quant.formal_name for quant in self.__quantities] assert new_quant.formal_name not in quant_formal_names self.__quantities.append(new_quant) new_name = new_quant.name # create a col with object data type; user must fill out column - col = pandas.DataFrame( index=list( self.__df.index), columns=[new_name], - dtype=object ) + col = pandas.DataFrame( + index=list(self.__df.index), columns=[new_name], dtype=object + ) tmp = self.__df - df = tmp.join(col, how='outer') - #self.__df = df.fillna(new_quant.value) + df = tmp.join(col, how="outer") + # self.__df = df.fillna(new_quant.value) def add_row(self, try_time_stamp, row_values): - ''' + """ Adds a row to the `DataFrame`, with a `timestamp` equal to `try_time_stamp` and row values equal to `row_values`. The length of `row_values` must match the number of columns in the data frame. @@ -394,30 +410,32 @@ def add_row(self, try_time_stamp, row_values): try_time_stamp: float row_values: list - ''' - assert try_time_stamp not in self.__df.index, 'already used time_stamp: %r'%\ - (try_time_stamp) + """ + assert try_time_stamp not in self.__df.index, "already used time_stamp: %r" % ( + try_time_stamp + ) assert isinstance(row_values, list) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is None, 'already used time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is None, "already used time_stamp: %r" % (try_time_stamp) time_stamp = try_time_stamp assert len(row_values) == self.__df.columns.size # create a row with object data type; users row_values data define data type - row = pandas.DataFrame( index=[time_stamp], - columns=list( self.__df.columns ), dtype=object ) + row = pandas.DataFrame( + index=[time_stamp], columns=list(self.__df.columns), dtype=object + ) - for (col,v) in zip(row.columns, row_values): - row.loc[time_stamp,col] = v + for col, v in zip(row.columns, row_values): + row.loc[time_stamp, col] = v frames = [self.__df, row] self.__df = pandas.concat(frames) return def get_row(self, try_time_stamp=None): - ''' + """ Returns an entire row of the phase dataframe. A row is a series of values that are all at the same time stamp. @@ -429,13 +447,13 @@ def get_row(self, try_time_stamp=None): ------- list(self.__df.loc[time_stamp, :]): list - ''' - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + """ + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) return list(self.__df.loc[time_stamp, :]) def get_column(self, actor): - ''' + """ Returns an entire column of data. A column is the entire history of data associated with a specific actor. @@ -447,14 +465,16 @@ def get_column(self, actor): ------- list(self.__df.loc[:, actor]): list - ''' + """ assert isinstance(actor, str) - assert actor in self.__df.columns, 'actor %r not in %r'% \ - (actor,self.__df.columns) + assert actor in self.__df.columns, "actor %r not in %r" % ( + actor, + self.__df.columns, + ) return list(self.__df.loc[:, actor]) def scale_row(self, try_time_stamp, value): - ''' + """ Multiplies all of the data in a row (except time stamp) by a scalar value. @@ -463,16 +483,16 @@ def scale_row(self, try_time_stamp, value): try_time_stamp: float value: float - ''' + """ assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) - #assert isinstance(value, int) or isinstance(value, float) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) + # assert isinstance(value, int) or isinstance(value, float) self.__df.loc[time_stamp, :] *= value return def ClearHistory(self, value=0.0): - ''' + """ Set species and quantities of history to a given value (default to zero value), all time stamps are preserved. @@ -480,14 +500,14 @@ def ClearHistory(self, value=0.0): ---------- value: float - ''' + """ assert isinstance(value, int) or isinstance(value, float) self.__df.loc[:, :] = value return def ResetHistory(self, try_time_stamp=None, value=None): - ''' + """ Set species and quantities of history to a given value (default to zero value) only one time stamp is preserved (default to last time stamp). @@ -497,36 +517,39 @@ def ResetHistory(self, try_time_stamp=None, value=None): try_time_stamp: float value: float - ''' + """ if value is not None: - assert isinstance(value, int) or isinstance(value, float) or \ - isinstance(value, np.ndarray) + assert ( + isinstance(value, int) + or isinstance(value, float) + or isinstance(value, np.ndarray) + ) if try_time_stamp is not None: - assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) + assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) values = self.GetRow(time_stamp) # save values columns = list(self.__df.columns) - assert len(columns) == len(values), 'FATAL: oops internal error.' + assert len(columns) == len(values), "FATAL: oops internal error." - self.__df = pandas.DataFrame( index=[time_stamp], columns=columns ) - self.__df.fillna( 0.0, inplace=True ) + self.__df = pandas.DataFrame(index=[time_stamp], columns=columns) + self.__df.fillna(0.0, inplace=True) if value is None: for v in values: idx = values.index(v) self.__df.loc[time_stamp, columns[idx]] = v # restore values else: - self.__df.loc[time_stamp, :] = value # set user-given value + self.__df.loc[time_stamp, :] = value # set user-given value return def get_value(self, actor, try_time_stamp=None): - ''' + """ Returns the value associated with a specified actor at a specified time stamp. @@ -540,31 +563,31 @@ def get_value(self, actor, try_time_stamp=None): ------- self.__df.loc[time_stamp, actor]: any - ''' + """ assert isinstance(actor, str) - assert actor in self.__df.columns, 'actor %r not in %r'% \ - (actor,self.__df.columns) + assert actor in self.__df.columns, "actor %r not in %r" % ( + actor, + self.__df.columns, + ) if try_time_stamp is not None: - assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) + assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) return self.__df.loc[time_stamp, actor] def set_value(self, actor, value, try_time_stamp=None): - ''' - - ''' + """ """ assert isinstance(actor, str) assert actor in self.__df.columns if try_time_stamp is not None: - assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) + assert isinstance(try_time_stamp, int) or isinstance(try_time_stamp, float) - time_stamp = self.__get_time_stamp( try_time_stamp ) - assert time_stamp is not None, 'missing try_time_stamp: %r'%(try_time_stamp) + time_stamp = self.__get_time_stamp(try_time_stamp) + assert time_stamp is not None, "missing try_time_stamp: %r" % (try_time_stamp) # Note: user value could have a different type than other column values. # If there is a type change, this will not be checked; user has been advised. @@ -573,14 +596,14 @@ def set_value(self, actor, value, try_time_stamp=None): return def write_html(self, fileName): - ''' + """ Convert the `Phase` container into an HTML file. Parameters --------- fileName: str - ''' + """ assert isinstance(fileName, str) tmp = pandas.DataFrame(self.__df) column_names = tmp.columns @@ -594,49 +617,56 @@ def write_html(self, fileName): elif col in quantity_names: idx = quantity_names.index(col) quant = self.__quantities[idx] - tmp.rename( columns={ col: col + '[' + quant.unit + ']'}, - inplace=True ) + tmp.rename(columns={col: col + "[" + quant.unit + "]"}, inplace=True) else: - assert False, 'oops fatal.' + assert False, "oops fatal." tmp.to_html(fileName) return def __str__(self): - s = '\n\t **Phase()**: name=%s;' + \ - '\n\t time unit: %s;' + \ - '\n\t *quantities*: %s;' + \ - '\n\t *species*: %s;' + \ - '\n\t *history* # time_stamps=%s;' + \ - '\n\t *history end* @%s;' + \ - '\n%s' - return s % (self.name, - self.__time_unit, - self.__quantities, - self.__species, - len(self.__df.index), - self.__df.index[-1], - self.__df.loc[self.__df.index[-1], :]) + s = ( + "\n\t **Phase()**: name=%s;" + + "\n\t time unit: %s;" + + "\n\t *quantities*: %s;" + + "\n\t *species*: %s;" + + "\n\t *history* # time_stamps=%s;" + + "\n\t *history end* @%s;" + + "\n%s" + ) + return s % ( + self.name, + self.__time_unit, + self.__quantities, + self.__species, + len(self.__df.index), + self.__df.index[-1], + self.__df.loc[self.__df.index[-1], :], + ) def __repr__(self): - s = '\n\t **Phase()**: name=%s;' + \ - '\n\t time unit: %s;' + \ - '\n\t *quantities*: %s;' + \ - '\n\t *species*: %s;' + \ - '\n\t *history* # time_stamps=%s;' + \ - '\n\t *history end* @%s;' + \ - '\n%s' - return s % (self.name, - self.__time_unit, - self.__quantities, - self.__species, - len(self.__df.index), - self.__df.index[-1], - self.__df.loc[self.__df.index[-1], :]) + s = ( + "\n\t **Phase()**: name=%s;" + + "\n\t time unit: %s;" + + "\n\t *quantities*: %s;" + + "\n\t *species*: %s;" + + "\n\t *history* # time_stamps=%s;" + + "\n\t *history end* @%s;" + + "\n%s" + ) + return s % ( + self.name, + self.__time_unit, + self.__quantities, + self.__species, + len(self.__df.index), + self.__df.index[-1], + self.__df.loc[self.__df.index[-1], :], + ) def __get_time_stamp(self, try_time_stamp=None): - ''' + """ Helper method for finding the closest time stamp to `try_time_stamp` in the phase history. The pandas index container used for storing float data type time stamps will return the nearest time stamp up to a @@ -653,7 +683,7 @@ def __get_time_stamp(self, try_time_stamp=None): self.__df.index[loc]: float or None Will return None if no time stamp within tolerance is found. - ''' + """ import numpy as np tol = 1.0e-3 @@ -663,58 +693,74 @@ def __get_time_stamp(self, try_time_stamp=None): else: time_stamps = np.array(self.__df.index) if time_stamps.size >= 2: - tol = 1.0e-3 * np.diff(time_stamps).mean() # 1e-3 * the mean delta t - try: # abs(index_value - try_time_stamp) <= tolerance - loc = self.__df.index.get_loc( try_time_stamp, method='nearest', - tolerance=tol ) - except KeyError: # no value found withing tol + tol = 1.0e-3 * np.diff(time_stamps).mean() # 1e-3 * the mean delta t + try: # abs(index_value - try_time_stamp) <= tolerance + loc = self.__df.index.get_loc( + try_time_stamp, method="nearest", tolerance=tol + ) + except KeyError: # no value found withing tol return None else: - return self.__df.index[loc] - - def plot_species(self, name, scaling=[1.0,1.0] , title=None, xlabel='Time [s]', - ylabel='y', legend='no-legend', filename_tag=None, figsize=[6,5], dpi=100 ): - - fig,ax=plt.subplots(1,figsize=figsize) - - x = np.array( [t for t in self.__df.index] ) + return self.__df.index[loc] + + def plot_species( + self, + name, + scaling=[1.0, 1.0], + title=None, + xlabel="Time [s]", + ylabel="y", + legend="no-legend", + filename_tag=None, + figsize=[6, 5], + dpi=100, + ): + fig, ax = plt.subplots(1, figsize=figsize) + + x = np.array([t for t in self.__df.index]) x *= float(scaling[0]) - y = np.array( self.get_column(name),dtype=np.float64 ) + y = np.array(self.get_column(name), dtype=np.float64) y *= float(scaling[1]) - yformatter = ScalarFormatter(useMathText=True,useOffset=True) + yformatter = ScalarFormatter(useMathText=True, useOffset=True) yformatter.set_powerlimits((15, 5)) ax.yaxis.set_major_formatter(yformatter) - ax.plot(x, y, 'b-', label=legend) + ax.plot(x, y, "b-", label=legend) - ax.set_xlabel(r''+xlabel,fontsize=16) - ax.set_ylabel(r''+ylabel,fontsize=16,color='black') - ax.tick_params(axis='y',labelsize=14) - ax.tick_params(axis='x',labelsize=14) - ax.legend(loc='best',fontsize=12) + ax.set_xlabel(r"" + xlabel, fontsize=16) + ax.set_ylabel(r"" + ylabel, fontsize=16, color="black") + ax.tick_params(axis="y", labelsize=14) + ax.tick_params(axis="x", labelsize=14) + ax.legend(loc="best", fontsize=12) if title: ax.set_title(title) elif self.get_species(name).info: - ax.set_title(self.get_species(name).info,fontsize=14) + ax.set_title(self.get_species(name).info, fontsize=14) ax.grid(True) - fig_name = name+'-'+self.name+'-phase-plot-' + fig_name = name + "-" + self.name + "-phase-plot-" if filename_tag: fig_name += filename_tag - fig.savefig(fig_name+'.png', dpi=dpi, fomat='png') + fig.savefig(fig_name + ".png", dpi=dpi, fomat="png") plt.close(fig) return - def plot( self, name='phase-plot-name', time_unit='s', legend=None, - nrows=2, ncols=2, dpi=200): - + def plot( + self, + name="phase-plot-name", + time_unit="s", + legend=None, + nrows=2, + ncols=2, + dpi=200, + ): num_var = len(self.__df.columns) if num_var == 0: return @@ -729,23 +775,27 @@ def plot( self, name='phase-plot-name', time_unit='s', legend=None, i_dash = 0 for i_var in range(num_var): # if multiple of nrows*ncols start new dashboard - if i_var % (nrows*ncols) == 0: - + if i_var % (nrows * ncols) == 0: if i_var != 0: # flush any current figure - fig_name = lead_name+'-'+self.name+'-phase-plot-' + \ - str(i_dash).zfill(2) - fig.savefig(fig_name+'.png', dpi=dpi, fomat='png') + fig_name = ( + lead_name + + "-" + + self.name + + "-phase-plot-" + + str(i_dash).zfill(2) + ) + fig.savefig(fig_name + ".png", dpi=dpi, fomat="png") plt.close(fig_num) - #pickle.dump( fig, open(fig_name+'.pickle','wb') ) + # pickle.dump( fig, open(fig_name+'.pickle','wb') ) i_dash += 1 - fig_num = str(np.random.random()) + '.' + str(i_dash) + fig_num = str(np.random.random()) + "." + str(i_dash) fig = plt.figure(num=fig_num) gs = gridspec.GridSpec(nrows, ncols) -# gs.update(left=0.08, right=0.98, wspace=0.4, hspace=0.4) + # gs.update(left=0.08, right=0.98, wspace=0.4, hspace=0.4) gs.update(left=0.11, right=0.98, wspace=0.4, hspace=0.5) axlst = list() @@ -763,8 +813,8 @@ def plot( self, name='phase-plot-name', time_unit='s', legend=None, axes = np.array(axlst) - text = today + ': Cortix.Phase.Plot' - fig.text(.5, .95, text, horizontalalignment='center', fontsize=14) + text = today + ": Cortix.Phase.Plot" + fig.text(0.5, 0.95, text, horizontalalignment="center", fontsize=14) axs = axes.flat @@ -791,9 +841,9 @@ def plot( self, name='phase-plot-name', time_unit='s', legend=None, else: assert self.__quantities[i_var].name == self.__df.columns[i_var] - varUnit = 'g/L' + varUnit = "g/L" - ''' + """ if varUnit == 'gram': varUnit = 'g' if varUnit == 'gram/min': @@ -806,33 +856,41 @@ def plot( self, name='phase-plot-name', time_unit='s', legend=None, varUnit = 'g/L' if varUnit == 'sec': varUnit = 's' - ''' + """ varLegend = legend - varScale = 'linear-linear' + varScale = "linear-linear" - assert varScale == 'log' or varScale == 'linear' or varScale == 'log-linear' \ - or varScale == 'linear-log' or varScale == 'linear-linear' or \ - varScale == 'log-log' + assert ( + varScale == "log" + or varScale == "linear" + or varScale == "log-linear" + or varScale == "linear-log" + or varScale == "linear-linear" + or varScale == "log-log" + ) - time_unit = 's' + time_unit = "s" - if time_unit == 'minute': - time_unit = 'min' + if time_unit == "minute": + time_unit = "min" - x = np.array( [i for i in self.__df.index] ) + x = np.array([i for i in self.__df.index]) - if (varScale == 'linear' or varScale == 'linear-linear' or \ - varScale == 'linear-log') and x.max() >= 60.0: + if ( + varScale == "linear" + or varScale == "linear-linear" + or varScale == "linear-log" + ) and x.max() >= 60.0: x /= 60.0 - if time_unit == 'min': - time_unit = 'h' - if time_unit == 'second' or time_unit=='s': - time_unit = 'min' + if time_unit == "min": + time_unit = "h" + if time_unit == "second" or time_unit == "s": + time_unit = "min" - y = np.array( self.__df[col_name] ) # convert to numpy ndarray + y = np.array(self.__df[col_name]) # convert to numpy ndarray - ''' + """ if (y.max() >= 1e3 or y.min() <= -1e3) and varScale != 'linear-log' and \ varScale != 'log-log' and varScale != 'log': y /= 1e3 @@ -974,12 +1032,12 @@ def plot( self, name='phase-plot-name', time_unit='s', legend=None, varUnit = 'ms' if varUnit == 'm/s': varUnit = 'mm/s' - ''' + """ - ax.set_xlabel('Time [' + time_unit + ']', fontsize=9) - ax.set_ylabel(varName + ' [' + varUnit + ']', fontsize=9) + ax.set_xlabel("Time [" + time_unit + "]", fontsize=9) + ax.set_ylabel(varName + " [" + varUnit + "]", fontsize=9) - ''' + """ ymax = y.max() dy = ymax * .1 ymax += dy @@ -991,7 +1049,7 @@ def plot( self, name='phase-plot-name', time_unit='s', legend=None, ymax = 1.0 ax.set_ylim(ymin, ymax) - ''' + """ if ncols >= 4: for l in ax.get_xticklabels(): @@ -1002,16 +1060,16 @@ def plot( self, name='phase-plot-name', time_unit='s', legend=None, for l in ax.get_yticklabels(): l.set_fontsize(10) - if time_unit == 'h' and x.max() - x.min() <= 5.0: + if time_unit == "h" and x.max() - x.min() <= 5.0: majorLocator = MultipleLocator(1.0) minorLocator = MultipleLocator(0.5) ax.xaxis.set_major_locator(majorLocator) ax.xaxis.set_minor_locator(minorLocator) - if varScale == 'log' or varScale == 'log-log': - ax.set_xscale('log') - ax.set_yscale('log') + if varScale == "log" or varScale == "log-log": + ax.set_xscale("log") + ax.set_yscale("log") positiveX = x > 0.0 x = np.extract(positiveX, x) y = np.extract(positiveX, y) @@ -1021,7 +1079,7 @@ def plot( self, name='phase-plot-name', time_unit='s', legend=None, if y.size > 0: if y.min() > 0.0 and y.max() > y.min(): ymax = y.max() - dy = ymax * .1 + dy = ymax * 0.1 ymax += dy ymin = y.min() ymin -= dy @@ -1033,22 +1091,22 @@ def plot( self, name='phase-plot-name', time_unit='s', legend=None, else: ax.set_ylim(1.0, 10.0) - if varScale == 'log-linear': - ax.set_xscale('log') - positiveX = x > 0.0 # True if > 0.0 + if varScale == "log-linear": + ax.set_xscale("log") + positiveX = x > 0.0 # True if > 0.0 x = np.extract(positiveX, x) y = np.extract(positiveX, y) - if varScale == 'linear-log': - ax.set_yscale('log') - positiveY = y > 0.0 # True if > 0.0 + if varScale == "linear-log": + ax.set_yscale("log") + positiveY = y > 0.0 # True if > 0.0 x = np.extract(positiveY, x) y = np.extract(positiveY, y) - #assert x.size == y.size, 'size error; stop.' + # assert x.size == y.size, 'size error; stop.' if y.size > 0: if y.min() > 0.0 and y.max() > y.min(): ymax = y.max() - dy = ymax * .1 + dy = ymax * 0.1 ymax += dy ymin = y.min() ymin -= dy @@ -1063,40 +1121,58 @@ def plot( self, name='phase-plot-name', time_unit='s', legend=None, # ................... # make the plot here - yformatter = ScalarFormatter(useMathText=True,useOffset=True) + yformatter = ScalarFormatter(useMathText=True, useOffset=True) yformatter.set_powerlimits((15, 5)) ax.yaxis.set_major_formatter(yformatter) if varLegend: - - ax.plot(x, y, 's-', color='black', linewidth=0.5, markersize=2, - markeredgecolor='black', label=varLegend) + ax.plot( + x, + y, + "s-", + color="black", + linewidth=0.5, + markersize=2, + markeredgecolor="black", + label=varLegend, + ) else: - - ax.plot(x, y, 's-', color='black', linewidth=0.5, markersize=2, - markeredgecolor='black') + ax.plot( + x, + y, + "s-", + color="black", + linewidth=0.5, + markersize=2, + markeredgecolor="black", + ) # ................... if species.info: - ax.set_title(species.info,fontsize=8) + ax.set_title(species.info, fontsize=8) if varLegend: - ax.legend(loc='best', prop={'size': 7}) + ax.legend(loc="best", prop={"size": 7}) ax.grid() # end of: for i_var in range(num_var): - fig_name = name+'-'+self.name+'-phase-plot-' + str(i_dash).zfill(2) - fig.savefig(fig_name+'.png', dpi=dpi, fomat='png') + fig_name = name + "-" + self.name + "-phase-plot-" + str(i_dash).zfill(2) + fig.savefig(fig_name + ".png", dpi=dpi, fomat="png") plt.close(fig_num) - #pickle.dump( fig, open(fig_name+'.pickle','wb') ) + # pickle.dump( fig, open(fig_name+'.pickle','wb') ) return -if __name__ == '__main__': - tbp_org = Species( name='TBP', formula_name='(C4H9O)_3PO(o)', - phase_name='organic', atoms=['12*C','27*H','4*O','P'] ) - quant = Quantity( name='volume' ) - phase = PhaseNew(name='solvent',species=[tbp_org],quantities=[quant]) + +if __name__ == "__main__": + tbp_org = Species( + name="TBP", + formula_name="(C4H9O)_3PO(o)", + phase_name="organic", + atoms=["12*C", "27*H", "4*O", "P"], + ) + quant = Quantity(name="volume") + phase = PhaseNew(name="solvent", species=[tbp_org], quantities=[quant]) print(phase) diff --git a/cortix/support/quantity.py b/cortix/support/quantity.py index 39d6df3e..ed89f9fb 100644 --- a/cortix/support/quantity.py +++ b/cortix/support/quantity.py @@ -5,10 +5,12 @@ import math import cmath import pandas -#import matplotlib -#matplotlib.use('Agg', warn=False) + +# import matplotlib +# matplotlib.use('Agg', warn=False) import matplotlib.pyplot as plt + class Quantity: """ todo: this probably should not have a "value" for the same reason as Species. @@ -17,183 +19,204 @@ class Quantity: value. For instance a history of the quantity as a time series. """ - def __init__(self, - name = 'null-quantity-name', - formalName = 'null-quantity-formal-name', # deprecated - formal_name = 'null-quantity-formal-name', - latex_name = 'null-quantity-latex-name', - value = float(0.0), # this can be any type - unit = 'null-quantity-unit', - info = 'null-quantity-info' - ): - - assert isinstance(name, str), 'not a string.' + + def __init__( + self, + name="null-quantity-name", + formalName="null-quantity-formal-name", # deprecated + formal_name="null-quantity-formal-name", + latex_name="null-quantity-latex-name", + value=float(0.0), # this can be any type + unit="null-quantity-unit", + info="null-quantity-info", + ): + assert isinstance(name, str), "not a string." self.__name = name - assert isinstance(formalName, str), 'not a string.' + assert isinstance(formalName, str), "not a string." self.__formalName = formalName # deprecated self.__formal_name = formalName - assert isinstance(formal_name, str), 'not a string.' + assert isinstance(formal_name, str), "not a string." self.__formal_name = formal_name - assert isinstance(latex_name, str), 'not a string.' + assert isinstance(latex_name, str), "not a string." self.__latex_name = latex_name self.__value = value - assert isinstance(name, str), 'not a string.' + assert isinstance(name, str), "not a string." self.__unit = unit self.__name = name self.__value = value self.__unit = unit - self.__info = info # info text such as technical name or other properties info + self.__info = info # info text such as technical name or other properties info return def SetName(self, n): - ''' + """ Sets the name of the quantity in question to n. Parameters ---------- n: str - ''' + """ self.__name = n + def get_name(self): - ''' + """ Returns the name of the quantity. Returns ------- name: str - ''' + """ return self.__name + name = property(get_name, SetName, None, None) def SetValue(self, v): - ''' + """ Sets the numerical value of the quantity to v. Parameters ---------- v: float - ''' + """ self.__value = v + def GetValue(self): - ''' + """ Gets the numerical value of the quantity. Returns ------- value: any type - ''' + """ return self.__value + value = property(GetValue, SetValue, None, None) def SetFormalName(self, fn): - - ''' + """ Sets the formal name of the property to fn. Parameters ---------- fn: str - ''' + """ self.__formalName = fn self.__formal_name = fn + def GetFormalName(self): - ''' + """ Returns the formal name of the quantity. Returns ------- formalName: str - ''' + """ - #return self.__formalName + # return self.__formalName return self.__formal_name + formalName = property(GetFormalName, SetFormalName, None, None) formal_name = property(GetFormalName, SetFormalName, None, None) def set_latex_name(self, ln): - - ''' + """ Sets the LaTeX name of the property to ln. Parameters ---------- ln: str - ''' + """ self.__latex_name = ln + def get_latex_name(self): - ''' + """ Returns the formal name of the quantity. Returns ------- formalName: str - ''' + """ return self.__latex_name + latex_name = property(get_latex_name, set_latex_name, None, None) def set_info(self, ln): - - ''' + """ Sets the LaTeX name of the property to ln. Parameters ---------- ln: str - ''' + """ self.__latex_name = ln + def get_info(self): - ''' + """ Returns the formal name of the quantity. Returns ------- formalName: str - ''' + """ return self.__info + info = property(get_info, set_info, None, None) def SetUnit(self, f): - ''' + """ Sets the units of the quantity to f (for example, density would be in units of g/cc. Parameters ---------- f: str - ''' + """ self.__unit = f + def GetUnit(self): - ''' + """ Returns the units of the quantity. Returns ------- unit: str - ''' + """ return self.__unit + unit = property(GetUnit, SetUnit, None, None) - def plot(self, x_scaling=1, y_scaling=1, y_shift=0, title=None, x_label='x', y_label=None, - file_name=None, same_axis=True, complex_form='polar', dpi=300): - ''' + def plot( + self, + x_scaling=1, + y_scaling=1, + y_shift=0, + title=None, + x_label="x", + y_label=None, + file_name=None, + same_axis=True, + complex_form="polar", + dpi=300, + ): + """ This will support a few possibities for data storage in the self.__value member. @@ -204,7 +227,7 @@ def plot(self, x_scaling=1, y_scaling=1, y_shift=0, title=None, x_label='x', y_l entry in the series. A plot of all elements in the type against the index of the series will be made. The plot may have all elements in one axis or each element in its own axis. - ''' + """ plt.clf() plt.cla() @@ -215,25 +238,25 @@ def plot(self, x_scaling=1, y_scaling=1, y_shift=0, title=None, x_label='x', y_l if len(self.__value) == 1: return - #print(type(self.__value)) - #print(self.__value) - #exit(0) + # print(type(self.__value)) + # print(self.__value) + # exit(0) if not title: title = self.info if not y_label: - if self.latex_name != 'null-quantity-latex-name': + if self.latex_name != "null-quantity-latex-name": y_label = self.latex_name - elif self.formal_name != 'null-quantity-formal-name': + elif self.formal_name != "null-quantity-formal-name": y_label = self.formal_name - elif self.name != 'null-quantity-name': + elif self.name != "null-quantity-name": y_label = self.name else: assert False - if self.unit != 'null-quantity-unit': - y_label += ' [' + self.unit + ']' + if self.unit != "null-quantity-unit": + y_label += " [" + self.unit + "]" complex_data = False @@ -242,30 +265,35 @@ def plot(self, x_scaling=1, y_scaling=1, y_shift=0, title=None, x_label='x', y_l # Turn series of values into a series of a list of one value to allow for # the indexing below plot_values = list() - for i in range (len(self.__value[:])): - #self.__value.iat[i] = [self.__value.iat[i]] # list of one element + for i in range(len(self.__value[:])): + # self.__value.iat[i] = [self.__value.iat[i]] # list of one element plot_values.append([self.__value.iat[i]]) # list of one element elif isinstance(self.__value[0], complex): n_dim = 1 complex_data = True same_axis = False - if complex_form == 'polar': - legend = [r'$|'+self.latex_name+'|$', r'$\phi$'] - y_label = r'$|V$| [' + self.unit + ']' - elif complex_form == 'rectangular': - legend = [r'$\Re('+self.latex_name+')$', r'$\Im('+self.latex_name+')$'] + if complex_form == "polar": + legend = [r"$|" + self.latex_name + "|$", r"$\phi$"] + y_label = r"$|V$| [" + self.unit + "]" + elif complex_form == "rectangular": + legend = [ + r"$\Re(" + self.latex_name + ")$", + r"$\Im(" + self.latex_name + ")$", + ] else: assert False plot_values = list() for i in range(len(self.__value[:])): z = self.__value.iat[i] - if complex_form == 'polar': + if complex_form == "polar": (mag, angle) = cmath.polar(z) - #self.__value.iat[i] = [mag, angle/math.pi*180] # list of two elements - plot_values.append([mag, angle/math.pi*180]) # list of two elements - elif complex_form == 'rectangular': - #self.__value.iat[i] = [z.real, z.imag] # list of two elements - plot_values.append([z.real, z.imag]) # list of two elements + # self.__value.iat[i] = [mag, angle/math.pi*180] # list of two elements + plot_values.append( + [mag, angle / math.pi * 180] + ) # list of two elements + elif complex_form == "rectangular": + # self.__value.iat[i] = [z.real, z.imag] # list of two elements + plot_values.append([z.real, z.imag]) # list of two elements elif len(self.__value[0]) >= 1: n_dim = len(self.__value[0]) plot_values = list() @@ -273,10 +301,10 @@ def plot(self, x_scaling=1, y_scaling=1, y_shift=0, title=None, x_label='x', y_l assert len(container) == n_dim plot_values.append(container) else: - assert False, 'not a valid data container in self.__value' + assert False, "not a valid data container in self.__value" - x = [i*x_scaling for i in self.__value.index] - #x = self.__value.index # potential bug in matplotlib + x = [i * x_scaling for i in self.__value.index] + # x = self.__value.index # potential bug in matplotlib # Warning: code needs review; complex valued data was introduced without testing @@ -284,43 +312,42 @@ def plot(self, x_scaling=1, y_scaling=1, y_shift=0, title=None, x_label='x', y_l fig = plt.figure(self.__formal_name) if complex_data: - fig,ax1 = plt.subplots() + fig, ax1 = plt.subplots() ax2 = ax1.twinx() for i in range(n_dim): - if not same_axis and not complex_data: - fig = plt.figure(self.__formal_name+str(i)) + fig = plt.figure(self.__formal_name + str(i)) y = list() for j in range(len(x)): - #y.append(self.__value.iat[j][i]) # must use iat() + # y.append(self.__value.iat[j][i]) # must use iat() y.append(plot_values[j][i]) - y = [(k-y_shift)*y_scaling for k in y] + y = [(k - y_shift) * y_scaling for k in y] if complex_data: y2 = list() for j in range(len(x)): - #y2.append(self.__value.iat[j][i+1]) # must use iat() - y2.append(plot_values[j][i+1]) + # y2.append(self.__value.iat[j][i+1]) # must use iat() + y2.append(plot_values[j][i + 1]) - y2 = [(k-y_shift)*y_scaling for k in y2] + y2 = [(k - y_shift) * y_scaling for k in y2] plt.title(title) if complex_data: - #print(x) - #print(y) - l1, = ax1.plot(x, y, color='blue') - #print(y2) - #print('') - l2, = ax2.plot(x, y2, color='red') + # print(x) + # print(y) + (l1,) = ax1.plot(x, y, color="blue") + # print(y2) + # print('') + (l2,) = ax2.plot(x, y2, color="red") ax1.set_xlabel(x_label) - ax1.set_ylabel(y_label, color='blue') - ax2.set_ylabel(r'$\phi$ [degree]', color='red') + ax1.set_ylabel(y_label, color="blue") + ax2.set_ylabel(r"$\phi$ [degree]", color="red") plt.legend([l1, l2], legend) else: plt.plot(x, y) @@ -330,35 +357,49 @@ def plot(self, x_scaling=1, y_scaling=1, y_shift=0, title=None, x_label='x', y_l plt.ylabel(y_label) if not same_axis and file_name: - plt.savefig(file_name+str(i)+'.png', dpi=dpi) + plt.savefig(file_name + str(i) + ".png", dpi=dpi) if same_axis and file_name: - plt.savefig(file_name+'.png',dpi=dpi) + plt.savefig(file_name + ".png", dpi=dpi) return def __str__(self): - ''' + """ Used to print the data stored by the quantity class. Will print out name, formal name, the value of the quantity and its unit. Returns ------- s: str - ''' - - s = '\n\t Quantity(): \n\t name=%s; formal name=%s; latex name=%s; info=%s; value=%s[%s]' - return s % (self.name, self.formal_name, self.latex_name, self.info, self.value, self.unit) + """ + + s = "\n\t Quantity(): \n\t name=%s; formal name=%s; latex name=%s; info=%s; value=%s[%s]" + return s % ( + self.name, + self.formal_name, + self.latex_name, + self.info, + self.value, + self.unit, + ) def __repr__(self): - ''' + """ Used to print the data stored by the quantity class. Will print out name, formal name, the value of the quantity and its unit. Returns ------- s: str - ''' - - s = '\n\t Quantity(): \n\t name=%s; formal name=%s; latex name=%s; info=%s; value=%s[%s]' - return s % (self.name, self.formal_name, self.latex_name, self.info, self.value, self.unit) + """ + + s = "\n\t Quantity(): \n\t name=%s; formal name=%s; latex name=%s; info=%s; value=%s[%s]" + return s % ( + self.name, + self.formal_name, + self.latex_name, + self.info, + self.value, + self.unit, + ) diff --git a/cortix/support/specie.py b/cortix/support/specie.py index 01f8f66c..116ada5d 100644 --- a/cortix/support/specie.py +++ b/cortix/support/specie.py @@ -8,7 +8,7 @@ # # Licensed under the University of Massachusetts Lowell LICENSE: # https://github.com/dpploy/cortix/blob/master/LICENSE.txt -''' +""" Author: Valmor de Almeida dealmeidav@ornl.gov; vfda This Specie class is to be used with other classes in plant-level process modules. @@ -50,44 +50,47 @@ can also reset the value of the molar mass with a setter method. Sat May 9 21:40:48 EDT 2015 created; vfda -''' -#********************************************************************************* +""" + +# ********************************************************************************* import os import sys from cortix.support.periodictable import ELEMENTS -#********************************************************************************* +# ********************************************************************************* + class Specie: - ''' + """ todo: phase should not be here; concentrations should not be here only molar quantities should be here see the Phase container - ''' - -#********************************************************************************* -# Construction -#********************************************************************************* - - def __init__(self, - name='null', - formula_name='null', - phase='null', - atoms=list(), - molarCC=0.0, # default unit: M (mole/L) - massCC=0.0, # default unit: g/L - flag=None): - - assert isinstance(name, str), 'oops not string.' + """ + + # ********************************************************************************* + # Construction + # ********************************************************************************* + + def __init__( + self, + name="null", + formula_name="null", + phase="null", + atoms=list(), + molarCC=0.0, # default unit: M (mole/L) + massCC=0.0, # default unit: g/L + flag=None, + ): + assert isinstance(name, str), "oops not string." self._name = name - assert isinstance(formula_name, str), 'oops not string.' + assert isinstance(formula_name, str), "oops not string." self.__formula_name = formula_name - assert isinstance(phase, str), 'oops not string.' + assert isinstance(phase, str), "oops not string." self._phase = phase - assert isinstance(atoms, list), 'oops not list.' + assert isinstance(atoms, list), "oops not list." self._atoms = atoms self._flag = flag # flag can be any type @@ -97,16 +100,16 @@ def __init__(self, self._molarGammaPwr = 0.0 self._molarRadioactivity = 0.0 - self._molarMassUnit = 'g/mole' + self._molarMassUnit = "g/mole" - self._molarHeatPwrUnit = 'W/mole' - self._molarGammaPwrUnit = 'W/mole' - self._molarRadioactivityUnit = 'Ci/mole' + self._molarHeatPwrUnit = "W/mole" + self._molarGammaPwrUnit = "W/mole" + self._molarRadioactivityUnit = "Ci/mole" self._molarRadioactivityFractions = list() - self._molarCCUnit = 'mole/L' - self._massCCUnit = 'g/L' + self._molarCCUnit = "mole/L" + self._massCCUnit = "g/L" self.__UpdateMolarMass() @@ -115,167 +118,174 @@ def __init__(self, self._massCC = 0.0 return - assert isinstance(molarCC, float), 'oops not a float.' - assert molarCC >= 0.0, 'oops negative value.' + assert isinstance(molarCC, float), "oops not a float." + assert molarCC >= 0.0, "oops negative value." self._molarCC = molarCC self._massCC = molarCC * self._molarMass - assert isinstance(massCC, float), 'oops not a float.' - assert massCC >= 0.0, 'oops negative value.' + assert isinstance(massCC, float), "oops not a float." + assert massCC >= 0.0, "oops negative value." if self._massCC == 0.0: self._massCC = massCC self._molarCC = massCC / self._molarMass return -#********************************************************************************* -# Public Member Functions -#********************************************************************************* + # ********************************************************************************* + # Public Member Functions + # ********************************************************************************* def GetName(self): - ''' + """ Returns the empirical name of the species. For example, "water". Returns ------- name: str - ''' + """ return self._name def SetName(self, n): - ''' + """ Sets the empirical name of the species to n. Parameters ---------- n: str - ''' + """ self._name = n + name = property(GetName, SetName, None, None) def GetFormulaName(self): - ''' + """ Returns the formulaic name of the compound. For example, "Dihydrogen monoxide". Returns ------- self.__formula_name: str - ''' + """ return self.__formula_name def SetFormulaName(self, f): - ''' + """ Sets the formulaic name to f. Returns ------- self.__formula_name: str - ''' + """ self.__formula_name = f + formula_name = property(GetFormulaName, SetFormulaName, None, None) def GetPhase(self): - ''' + """ Returns the phase that the specie is in. Returns ------- phase: str - ''' + """ return self._phase def SetPhase(self, p): - ''' + """ Sets the phase history to p. Parameters ---------- p: dataFrame - ''' + """ self._mass = p + phase = property(GetPhase, SetPhase, None, None) def GetMolarMass(self): - ''' + """ Returns the numerical value for the molar mass of the species. Units are given by molarMassUnit. Returns ------- molarMass: float - ''' + """ return self._molarMass def SetMolarMass(self, v): - ''' + """ Sets the molar mass of the species equal to v. Parameters ---------- v: float - ''' + """ self._molarMass = v + molarMass = property(GetMolarMass, SetMolarMass, None, None) def GetMolarMassUnit(self): - ''' + """ Returns the unit used to measure the molar mass of the species. Returns ------- molarMassUnit: str - ''' + """ return self._molarMassUnit def SetMolarMassUnit(self, v): - ''' + """ Sets the unit used to measure the molar mass of the species to v. Parameters ---------- v: str - ''' + """ self._molarMassUnit = v + molarMassUnit = property(GetMolarMassUnit, SetMolarMassUnit, None, None) def GetMolarRadioactivity(self): - ''' + """ Returns the numerical value for molar radioactivity of the species. Returns ------- molarRadioactivity: float - ''' + """ return self._molarRadioactivity def SetMolarRadioactivity(self, v): - ''' + """ Sets the molar radioactivity of the species equal to v. Parameters ---------- v: float - ''' + """ self._molarRadioactivity = v - molarRadioactivity = property( GetMolarRadioactivity, SetMolarRadioactivity, - None, None) + + molarRadioactivity = property( + GetMolarRadioactivity, SetMolarRadioactivity, None, None + ) def GetMolarRadioactivityFractions(self): - ''' + """ Returns a list of numbers that speciefies the % of molar reactivity that comes from each type of atom in the species. For example, a molarRadioactivityFraction of [0.65, 0.35] for water means that 65% @@ -285,12 +295,12 @@ def GetMolarRadioactivityFractions(self): Returns ------- molarRadioactivityFractions: list - ''' + """ return self._molarRadioactivityFractions def SetMolarRadioactivityFractions(self, fracs): - ''' + """ Sets molarRadioactivityFractions equal to fracs. Fracs must be a list of floats with the same length as there are different atoms in the species, or the function call will fail. (e.g. self._atoms and fracs @@ -302,169 +312,175 @@ def SetMolarRadioactivityFractions(self, fracs): Parameters ---------- fracs: list - ''' + """ - assert isinstance(fracs, list), 'oops not list.' + assert isinstance(fracs, list), "oops not list." if len(fracs) > 0: - assert len(fracs) == len(self._atoms), 'oops not right length,' + assert len(fracs) == len(self._atoms), "oops not right length," if len(fracs) != 0: - assert isinstance(fracs[-1], float), 'oops not float.' + assert isinstance(fracs[-1], float), "oops not float." self._molarRadioactivityFractions = fracs - molarRadioactivityFractions = property( GetMolarRadioactivityFractions, - SetMolarRadioactivityFractions, None, None) + + molarRadioactivityFractions = property( + GetMolarRadioactivityFractions, SetMolarRadioactivityFractions, None, None + ) def GetMolarRadioactivityUnit(self): - ''' + """ Returns the unit used to measure molar radioactivity. Returns ------- molarRadioactivityUnit: str - ''' + """ return self._molarRadioactivityUnit def SetMolarRadioactivityUnit(self, v): - ''' + """ Sets the unit used to measure molar radioactivity to v. Parameters ---------- v: str - ''' + """ self._molarRadioactivityUnit = v - molarRadioactivityUnit = property( GetMolarRadioactivityUnit, - SetMolarRadioactivityUnit, None, None) + + molarRadioactivityUnit = property( + GetMolarRadioactivityUnit, SetMolarRadioactivityUnit, None, None + ) def GetMolarHeatPwr(self): - ''' + """ Returns the amount of heat generated per mole of this species. Returns ------- molarHeatPwr: float - ''' + """ return self._molarHeatPwr def SetMolarHeatPwr(self, v): - ''' + """ Sets the amount of heat generated per mole of this species to v. Parameters ---------- v: float - ''' + """ self._molarHeatPwr = v + molarHeatPwr = property(GetMolarHeatPwr, SetMolarHeatPwr, None, None) def GetMolarHeatPwrUnit(self): - ''' + """ Returns the unit used to measure the amount of heat generated per mole of this species. Returns ------- molarHeatPwrUnit: str - ''' + """ return self._molarHeatPwrUnit def SetMolarHeatPwrUnit(self, v): - ''' + """ Sets the unit used to measure the amount of heat generated per mole of this species to v. Parameters ---------- v: str - ''' + """ self._molarHeatPwrUnit = v - molarHeatPwrUnit = property( GetMolarHeatPwrUnit, SetMolarHeatPwrUnit, None, - None) + + molarHeatPwrUnit = property(GetMolarHeatPwrUnit, SetMolarHeatPwrUnit, None, None) def GetMolarGammaPwr(self): - ''' + """ Returns the amount of gamma radiation produced per mole of this species (measured in units of power). Returns ------- molarGammaPwr: float - ''' + """ return self._molarGammaPwr def SetMolarGammaPwr(self, v): - ''' + """ Sets the amount of gamma radiation produced per mole of this species to v. Parameters ---------- v: float - ''' + """ self._molarGammaPwr = v + molarGammaPwr = property(GetMolarGammaPwr, SetMolarGammaPwr, None, None) def GetMolarGammaPwrUnit(self): - ''' + """ Returns the unit used to measure the amount of gamma radiation produced per mole of this species. Returns ------- molarGammaPwrUnit: str - ''' + """ return self._molarGammaPwrUnit def SetMolarGammaPwrUnit(self, v): - ''' + """ Sets the unit used to measure the amount of gamma radiation produced per mole of this species to v. Parameters ---------- v: str - ''' + """ self._molarGammaPwrUnit = v - molarGammaPwrUnit = property( GetMolarGammaPwrUnit, SetMolarGammaPwrUnit, None, - None) + + molarGammaPwrUnit = property(GetMolarGammaPwrUnit, SetMolarGammaPwrUnit, None, None) # Deprecated; see new interface below as GetFormula def GetAtoms(self): return self._atoms def SetAtoms(self, atoms): - - assert isinstance(atoms, list), 'oops not list.' + assert isinstance(atoms, list), "oops not list." if len(atoms) != 0: - assert isinstance(atoms[-1], str), 'oops not string.' + assert isinstance(atoms[-1], str), "oops not string." self._atoms = atoms self.__UpdateMolarMass() + atoms = property(GetAtoms, SetAtoms, None, None) # New interface def GetFormula(self): - ''' + """ Returns the molecular or empirical formula of the species. It is usually a list, for example, of the form ['2*H', 'O']. Returns ------- formula: list - ''' + """ return self._atoms def SetFormula(self, atoms): - ''' + """ Sets the species' formula equal to atoms. Will automatically update the molar mass of the species, and will also fail if atoms is not a list of strings. @@ -472,31 +488,33 @@ def SetFormula(self, atoms): Parameters ---------- atoms: list - ''' + """ - assert isinstance(atoms, list), 'oops not list.' + assert isinstance(atoms, list), "oops not list." if len(atoms) != 0: - assert isinstance(atoms[-1], str), 'oops not string.' + assert isinstance(atoms[-1], str), "oops not string." self._atoms = atoms self.__UpdateMolarMass() + formula = property(GetFormula, SetFormula, None, None) def GetNAtoms(self): # number of ficticious atoms in the species (see NB above) - ''' + """ Returns the total number of atoms comprising the species. For example, water is comprised of three atoms. Returns ------- nAtoms: int - ''' + """ return self._nAtoms + nAtoms = property(GetNAtoms, None, None, None) # number of nuclide types involved in the species definition def GetNNuclideTypes(self): - ''' + """ Returns the number of different types of atoms comprising the species. For example, water is composed of two different types of atoms, hydrogen and oxygen. @@ -504,187 +522,261 @@ def GetNNuclideTypes(self): Returns ------- nNuclideTypes: int - ''' + """ return self._nNuclideTypes + nNuclideTypes = property(GetNNuclideTypes, None, None, None) def SetFlag(self, f): - ''' + """ Sets the flag associated with the species to f. Parameters ---------- f: str - ''' + """ self._flag = f def GetFlag(self): - ''' + """ Returns the flag associated with the species. Returns ------- flag: str - ''' + """ return self._flag + flag = property(GetFlag, SetFlag, None, None) def GetMolarCC(self): - ''' + """ Returns the numerical value for the number (molar) density of the species (moles/volume). Returns ------- molarCC: float - ''' + """ return self._molarCC def SetMolarCC(self, v): - ''' + """ Sets the numerical value for the molar density of the species to v. Parameters ---------- v: float - ''' + """ self._molarCC = v self._massCC = v * self._molarMass + molarCC = property(GetMolarCC, SetMolarCC, None, None) def GetMolarCCUnit(self): - ''' + """ Returns the unit used to measure molar density of the species. Returns ------- molarCCUnit: str - ''' + """ return self._molarCCUnit def SetMolarCCUnit(self, v): - ''' + """ Sets the unit used to measure the molar density of the species to v. Parameters ---------- v: str - ''' + """ self._molarCCUnit = v + molarCCUnit = property(GetMolarCCUnit, SetMolarCCUnit, None, None) def GetMassCC(self): - ''' + """ Returns the numerical value of the mass density of the species (mass/volume). Returns ------- massCC: float - ''' + """ return self._massCC def SetMassCC(self, v): - ''' + """ Sets the numerical value of the mass density equal to v. Parameters ---------- v: float - ''' + """ self._massCC = v if self._molarMass == 0.0 and v == 0.0: self._molarCC = 0.0 else: self._molarCC = v / self._molarMass + massCC = property(GetMassCC, SetMassCC, None, None) def GetMassCCUnit(self): - ''' + """ Returns the unit used to measure the mass density of the species. Returns ------- massCCUnit: str - ''' + """ return self._massCCUnit def SetMassCCUnit(self, v): - ''' + """ Sets the units used to measure mass density to v. Parameters ---------- v: str - ''' + """ self._massCCUnit = v + massCCUnit = property(GetMassCCUnit, SetMassCCUnit, None, None) def __str__(self): - s = '\n\n\t \n' - return s%(self.name, self.__formula_name, self.phase, self.__ReorderFormula(), self.nAtoms, - self.nNuclideTypes, self.molarMass, self.molarMassUnit, self.molarCC, self.molarCCUnit, - self.massCC, self.massCCUnit, self.flag, self.molarRadioactivity, - self.molarRadioactivityUnit, self.molarRadioactivity * self.molarCC, '[Ci/cc]', - self.molarHeatPwr, self.molarHeatPwrUnit, self.molarHeatPwr * self.molarCC, '[W/cc]', - self.molarGammaPwr, self.molarGammaPwrUnit, self.molarGammaPwr * self.molarCC, '[W/cc]', - [i.split('*')[-1] for i in self.formula], - ['%9.3e' % i for i in self.molarRadioactivityFractions]) + s = ( + "\n\n\t \n" + ) + return s % ( + self.name, + self.__formula_name, + self.phase, + self.__ReorderFormula(), + self.nAtoms, + self.nNuclideTypes, + self.molarMass, + self.molarMassUnit, + self.molarCC, + self.molarCCUnit, + self.massCC, + self.massCCUnit, + self.flag, + self.molarRadioactivity, + self.molarRadioactivityUnit, + self.molarRadioactivity * self.molarCC, + "[Ci/cc]", + self.molarHeatPwr, + self.molarHeatPwrUnit, + self.molarHeatPwr * self.molarCC, + "[W/cc]", + self.molarGammaPwr, + self.molarGammaPwrUnit, + self.molarGammaPwr * self.molarCC, + "[W/cc]", + [i.split("*")[-1] for i in self.formula], + ["%9.3e" % i for i in self.molarRadioactivityFractions], + ) def __repr__(self): - s = '\n\n\t Specie(): name=%s;' + ' formula_name=%s;' + ' phase=%s;' + '\n\t formula=%s;' +\ - '\n\t # atoms=%s;' + ' # nuclide types=%s;' + ' molar mass=%9.3e[%s];' +\ - ' molar cc=%9.3e[%s];' + ' mass cc=%9.3e[%s];' + '\n\t flag=%s;' +\ - '\n\t molar radioactivity=%9.3e[%s];' + '\n\t radioactivity dens.=%9.3e[%s];' +\ - '\n\t molar heat pwr=%9.3e[%s];' + '\n\t heat pwr dens.=%9.3e[%s];' +\ - '\n\t molar gamma pwr=%9.3e[%s];' + '\n\t gamma pwr dens.=%9.3e[%s];' +\ - '\n\t atoms=%s;' + '\n\t molar radioactivity fractions=%s\n' - return s%(self.name, self.__formula_name, self.phase, self.__ReorderFormula(), self.nAtoms, - self.nNuclideTypes, self.molarMass, self.molarMassUnit, self.molarCC, self.molarCCUnit, - self.massCC, self.massCCUnit, self.flag, self.molarRadioactivity, - self.molarRadioactivityUnit, self.molarRadioactivity * self.molarCC, '[Ci/cc]', - self.molarHeatPwr, self.molarHeatPwrUnit, self.molarHeatPwr * self.molarCC, '[W/cc]', - self.molarGammaPwr, self.molarGammaPwrUnit, self.molarGammaPwr * self.molarCC, '[W/cc]', - [i.split('*')[-1] for i in self.formula], - ['%9.3e' % i for i in self.molarRadioactivityFractions]) - -#********************************************************************************* -# Private Helper Functions (Internal use: __) -#********************************************************************************* + s = ( + "\n\n\t Specie(): name=%s;" + + " formula_name=%s;" + + " phase=%s;" + + "\n\t formula=%s;" + + "\n\t # atoms=%s;" + + " # nuclide types=%s;" + + " molar mass=%9.3e[%s];" + + " molar cc=%9.3e[%s];" + + " mass cc=%9.3e[%s];" + + "\n\t flag=%s;" + + "\n\t molar radioactivity=%9.3e[%s];" + + "\n\t radioactivity dens.=%9.3e[%s];" + + "\n\t molar heat pwr=%9.3e[%s];" + + "\n\t heat pwr dens.=%9.3e[%s];" + + "\n\t molar gamma pwr=%9.3e[%s];" + + "\n\t gamma pwr dens.=%9.3e[%s];" + + "\n\t atoms=%s;" + + "\n\t molar radioactivity fractions=%s\n" + ) + return s % ( + self.name, + self.__formula_name, + self.phase, + self.__ReorderFormula(), + self.nAtoms, + self.nNuclideTypes, + self.molarMass, + self.molarMassUnit, + self.molarCC, + self.molarCCUnit, + self.massCC, + self.massCCUnit, + self.flag, + self.molarRadioactivity, + self.molarRadioactivityUnit, + self.molarRadioactivity * self.molarCC, + "[Ci/cc]", + self.molarHeatPwr, + self.molarHeatPwrUnit, + self.molarHeatPwr * self.molarCC, + "[W/cc]", + self.molarGammaPwr, + self.molarGammaPwrUnit, + self.molarGammaPwr * self.molarCC, + "[W/cc]", + [i.split("*")[-1] for i in self.formula], + ["%9.3e" % i for i in self.molarRadioactivityFractions], + ) + + # ********************************************************************************* + # Private Helper Functions (Internal use: __) + # ********************************************************************************* def __UpdateMolarMass(self): - ''' + """ Updates the molar mass of the species after the molecular formula has been changed. - ''' + """ - #if len(self._atoms) == 0: + # if len(self._atoms) == 0: # self._nAtoms = 0 # return for entry in self._atoms: - assert isinstance(entry, str), 'oops' - tmp = entry.split('*') + assert isinstance(entry, str), "oops" + tmp = entry.split("*") nuclide = tmp[-1] - element = nuclide.split('-')[0] - assert element in ELEMENTS, 'element = %r' % (element) + element = nuclide.split("-")[0] + assert element in ELEMENTS, "element = %r" % (element) self._nAtoms = 0 self._nNuclideTypes = 0 @@ -694,9 +786,9 @@ def __UpdateMolarMass(self): nAtoms = 0 summ = 0.0 for entry in self._atoms: - assert isinstance(entry, str), 'oops' + assert isinstance(entry, str), "oops" # format example: 3.2*O-18, or 3*O or O or O-16 - tmp = entry.split('*') + tmp = entry.split("*") multiple = 1.0 # single nuclide if len(tmp) == 1: @@ -712,14 +804,14 @@ def __UpdateMolarMass(self): nAtoms += multiple try: - tmp = nuclide.split('-') + tmp = nuclide.split("-") if len(tmp) == 1: element = ELEMENTS[tmp[0]] molarMass = element.exactmass # from isotopic composition if molarMass == 0.0: molarMass = element.mass elif len(tmp) == 2: - element = ELEMENTS[tmp[0]].isotopes[int(tmp[1].strip('m'))] + element = ELEMENTS[tmp[0]].isotopes[int(tmp[1].strip("m"))] molarMass = element.mass else: assert False @@ -729,14 +821,14 @@ def __UpdateMolarMass(self): summ += multiple * molarMass self._molarMass = summ -# print( summ ) + # print( summ ) self._nAtoms = nAtoms self._nNuclideTypes = len(nuclides) return def __ReorderFormula(self): - ''' + """ Takes a list of atoms for a molecular or empirical formula and places it in order of decreasing magnitude of stoichiometric coefficient. For example, [O, 2*H] will be returned as [2*H, O]. @@ -744,7 +836,7 @@ def __ReorderFormula(self): Returns ------- atoms2: list - ''' + """ atoms1 = self._atoms[:] # shallow copy atoms2 = list() @@ -753,14 +845,12 @@ def __ReorderFormula(self): return atoms1 if len(self._atoms) > 1: - # save the multiplier value as a string type of scientific notation for entry in self._atoms: - - assert isinstance(entry, str), 'oops' + assert isinstance(entry, str), "oops" # format example: 3.2*O-18, or 3*O or O or O-16 - tmp = entry.split('*') + tmp = entry.split("*") multiplier = 0.0 @@ -771,18 +861,17 @@ def __ReorderFormula(self): else: assert False - assert multiplier != 0.0, 'multiplier = %r' % (multiplier) + assert multiplier != 0.0, "multiplier = %r" % (multiplier) - multiplier = '{0:9.3e}'.format(multiplier) + multiplier = "{0:9.3e}".format(multiplier) - atoms1[self._atoms.index(entry)] = multiplier + '*' + tmp[1] + atoms1[self._atoms.index(entry)] = multiplier + "*" + tmp[1] # order in decreasing order of multiplier magnitude multipliers_lst = list() for entry in atoms1: - - tmp = entry.split('*') + tmp = entry.split("*") multiplier = 0.0 @@ -795,12 +884,16 @@ def __ReorderFormula(self): multipliers_lst.append(float(multiplier)) - sortedAtoms_lst = [a for (i, a) in sorted(zip(multipliers_lst, atoms1), - key=lambda pair: pair[0], - reverse=True)] + sortedAtoms_lst = [ + a + for (i, a) in sorted( + zip(multipliers_lst, atoms1), key=lambda pair: pair[0], reverse=True + ) + ] atoms2 = sortedAtoms_lst return atoms2 -#============================= end class Specie ================================== + +# ============================= end class Specie ================================== diff --git a/cortix/support/species.py b/cortix/support/species.py index 97fe87e9..2fc67e4f 100644 --- a/cortix/support/species.py +++ b/cortix/support/species.py @@ -9,6 +9,7 @@ import scipy.constants as const from cortix.support.periodictable import ELEMENTS + class Species: """ All SI units (kg,s,K,Pa,J,W). @@ -49,15 +50,17 @@ class Species: """ - def __init__( self, - name='no-species-name', - formula_name='no-species-formula-name', - atoms=None, - charge=None, - phase=None, - flag='no-species-flag', - info=None, - latex_name='no-species-latex-name'): + def __init__( + self, + name="no-species-name", + formula_name="no-species-formula-name", + atoms=None, + charge=None, + phase=None, + flag="no-species-flag", + info=None, + latex_name="no-species-latex-name", + ): """Constructs a Species object. Parameters @@ -113,36 +116,39 @@ def __init__( self, assert isinstance(charge, int) self.charge = charge else: - self.charge = 0 # defaults to neutral species + self.charge = 0 # defaults to neutral species if phase is not None: assert isinstance(phase, str) - self.phase = phase # defaults to None + self.phase = phase # defaults to None self.flag = flag # flag can be any type - self.info = info # info text such as technical name or other properties info + self.info = info # info text such as technical name or other properties info assert isinstance(latex_name, str) self.latex_name = latex_name - self.molar_mass = 0.0 # kg/mol + self.molar_mass = 0.0 # kg/mol self.molar_heat_pwr = 0.0 self.molar_gamma_pwr = 0.0 self.molar_radioactivity = 0.0 - self.molar_mass_unit = 'kg/mol' + self.molar_mass_unit = "kg/mol" - self.molar_heat_pwr_unit = 'W/mol' - self.molar_gamma_pwr_unit = 'W/mol' - self.molar_radioactivity_unit = 'Ci/mol' + self.molar_heat_pwr_unit = "W/mol" + self.molar_gamma_pwr_unit = "W/mol" + self.molar_radioactivity_unit = "Ci/mol" self.molar_radioactivity_fractions = list() - if self.latex_name == 'no-species-latex-name' and self.formula_name != 'no-species-formula-name': + if ( + self.latex_name == "no-species-latex-name" + and self.formula_name != "no-species-formula-name" + ): self.__latex_name_from_formula_name() - if len(self.atoms) == 0 and self.formula_name != 'no-species-formula-name': + if len(self.atoms) == 0 and self.formula_name != "no-species-formula-name": self.__atoms_from_formula_name() self.update_molar_mass() @@ -155,17 +161,17 @@ def update_molar_mass(self): """ - molar_mass_const = const.physical_constants['molar mass constant'][0] - #molar_mass_const_unit = const.physical_constants['molar mass constant'][1] + molar_mass_const = const.physical_constants["molar mass constant"][0] + # molar_mass_const_unit = const.physical_constants['molar mass constant'][1] for entry in self.atoms: assert isinstance(entry, str) - tmp = entry.split('*') + tmp = entry.split("*") nuclide = tmp[-1] - element = nuclide.split('-')[0] + element = nuclide.split("-")[0] if not element in ELEMENTS: - print('Warning: not a chemical element %s'%element) - #assert element in ELEMENTS, 'element = %r'%(element) + print("Warning: not a chemical element %s" % element) + # assert element in ELEMENTS, 'element = %r'%(element) self.num_atoms = 0 self.num_nuclide_types = 0 @@ -177,7 +183,7 @@ def update_molar_mass(self): for entry in self.atoms: assert isinstance(entry, str) # format example: 3.2*O-18, or 3*O or O or O-16 - tmp = entry.split('*') + tmp = entry.split("*") multiple = 1.0 # single nuclide if len(tmp) == 1: @@ -193,7 +199,7 @@ def update_molar_mass(self): num_atoms += multiple try: - tmp = nuclide.split('-') + tmp = nuclide.split("-") if len(tmp) == 1: element = ELEMENTS[tmp[0]] rel_atomic_mass = element.exactmass # from isotopic composition @@ -201,7 +207,7 @@ def update_molar_mass(self): rel_atomic_mass = element.mass molar_mass = rel_atomic_mass * molar_mass_const elif len(tmp) == 2: - element = ELEMENTS[tmp[0]].isotopes[int(tmp[1].strip('m'))] + element = ELEMENTS[tmp[0]].isotopes[int(tmp[1].strip("m"))] molar_mass = element.mass * molar_mass_const else: assert False @@ -216,16 +222,18 @@ def update_molar_mass(self): self.num_nuclide_types = len(nuclides) # Correct molar mass of species for mass of electron - self.molar_mass += self.charge * const.physical_constants['electron molar mass'][0] + self.molar_mass += ( + self.charge * const.physical_constants["electron molar mass"][0] + ) - #if self.charge > 0: + # if self.charge > 0: # self.molar_mass -= self.charge * const.physical_constants['electron molar mass'][0] - #else: + # else: # self.molar_mass += -1 * self.charge * const.physical_constants['electron molar mass'][0] # Exception: e^- (solvated electron) - if self.formula_name == 'e^-': - self.molar_mass = const.physical_constants['electron molar mass'][0] + if self.formula_name == "e^-": + self.molar_mass = const.physical_constants["electron molar mass"][0] def ordered_atoms_list(self): """Sorted list of the atoms in the species; mostly for printing purposes. @@ -248,14 +256,12 @@ def ordered_atoms_list(self): return atoms1 if len(self.atoms) > 1: - # save the multiplier value as a string type of scientific notation for id, entry in enumerate(self.atoms): - assert isinstance(entry, str) # format example: 3.2*O-18, or 3*O or O or O-16 - tmp = entry.split('*') + tmp = entry.split("*") nuclide = tmp[-1] @@ -268,19 +274,18 @@ def ordered_atoms_list(self): else: assert False - assert multiplier != 0.0, 'multiplier = %r' % (multiplier) + assert multiplier != 0.0, "multiplier = %r" % (multiplier) - multiplier = '{0:9.3e}'.format(multiplier) + multiplier = "{0:9.3e}".format(multiplier) - #atoms1[self.atoms.index(entry)] = multiplier + '*' + nuclide - atoms1[id] = multiplier + '*' + nuclide + # atoms1[self.atoms.index(entry)] = multiplier + '*' + nuclide + atoms1[id] = multiplier + "*" + nuclide # order in decreasing order of multiplier magnitude multipliers_lst = list() for entry in atoms1: - - tmp = entry.split('*') + tmp = entry.split("*") multiplier = 0.0 @@ -293,8 +298,12 @@ def ordered_atoms_list(self): multipliers_lst.append(float(multiplier)) - sorted_atoms = [a for (i, a) in sorted(zip(multipliers_lst, atoms1), - key=lambda pair: pair[0], reverse=True)] + sorted_atoms = [ + a + for (i, a) in sorted( + zip(multipliers_lst, atoms1), key=lambda pair: pair[0], reverse=True + ) + ] atoms2 = sorted_atoms @@ -319,37 +328,37 @@ def __atoms_from_formula_name(self): formula_name = self.formula_name.strip() # Remove radical symbol * (there can only be one) - formula_name = formula_name.strip('^*') - tmp = formula_name.split('^*') + formula_name = formula_name.strip("^*") + tmp = formula_name.split("^*") if len(tmp) > 1: assert len(tmp) == 2, 'fatal: only one "^*" allowed.' - formula_name = tmp[0]+tmp[1] + formula_name = tmp[0] + tmp[1] # Remove phase indicator - i = formula_name.find('(') # first index - j = formula_name.rfind(')') # highest index + i = formula_name.find("(") # first index + j = formula_name.rfind(")") # highest index if i != -1 and j != -1: - self.phase = formula_name[i:j+1] - formula_name = formula_name.replace(formula_name[i:j+1], '') + self.phase = formula_name[i : j + 1] + formula_name = formula_name.replace(formula_name[i : j + 1], "") elif (i == -1 and j != -1) or (i != -1 and j == -1): assert False, 'fatal: missing pairing ")".' # Remove complexation * indicator (there can be more than one) - formula_name = formula_name.strip('*') - tmp = formula_name.split('*') + formula_name = formula_name.strip("*") + tmp = formula_name.split("*") for frag in tmp: - assert frag[-1] != '^', 'fomula_name = %r; tmp = %r'%(formula_name, tmp) - formula_name = formula_name.replace('*', '') + assert frag[-1] != "^", "fomula_name = %r; tmp = %r" % (formula_name, tmp) + formula_name = formula_name.replace("*", "") # Remove charge - i = formula_name.find('^') - if i != -1: # if success - if formula_name[i+1].isnumeric(): # integer followed by sign - charge = formula_name[i+1:i+3] - formula_name = formula_name.replace(formula_name[i:i+3], '') - else: # just a sign - charge = formula_name[i+1:i+2] - formula_name = formula_name.replace(formula_name[i:i+2], '') + i = formula_name.find("^") + if i != -1: # if success + if formula_name[i + 1].isnumeric(): # integer followed by sign + charge = formula_name[i + 1 : i + 3] + formula_name = formula_name.replace(formula_name[i : i + 3], "") + else: # just a sign + charge = formula_name[i + 1 : i + 2] + formula_name = formula_name.replace(formula_name[i : i + 2], "") sign = charge[-1] @@ -358,26 +367,28 @@ def __atoms_from_formula_name(self): elif len(charge) == 1: val = 1 else: - assert False, 'fatal: invalid charge = %r'%charge + assert False, "fatal: invalid charge = %r" % charge - if sign == '-': + if sign == "-": self.charge = -1 * val else: self.charge = val # Find atom group multiplicity recursively - while formula_name.find('[') > 1 or formula_name.find(']') > 1: - i = formula_name.rfind('[') # innermost [ - j = formula_name.find(']',i,len(formula_name)) # matching ] - if i != -1 and j != -1: # if success - assert formula_name[j+1].isnumeric() - sub_formula_name = formula_name[i+1:j] - right_side_of_sub_formula = formula_name[j+1:] - multiplicity = int(formula_name[j+1]) - formula_name = formula_name[:j+1] - formula_name = formula_name.replace(formula_name[i:j+1], multiplicity*sub_formula_name) + while formula_name.find("[") > 1 or formula_name.find("]") > 1: + i = formula_name.rfind("[") # innermost [ + j = formula_name.find("]", i, len(formula_name)) # matching ] + if i != -1 and j != -1: # if success + assert formula_name[j + 1].isnumeric() + sub_formula_name = formula_name[i + 1 : j] + right_side_of_sub_formula = formula_name[j + 1 :] + multiplicity = int(formula_name[j + 1]) + formula_name = formula_name[: j + 1] + formula_name = formula_name.replace( + formula_name[i : j + 1], multiplicity * sub_formula_name + ) formula_name += right_side_of_sub_formula[1:] - elif (i >= 0 and j == -1) or (i == -1 and j >=0): + elif (i >= 0 and j == -1) or (i == -1 and j >= 0): assert False # Build the atom list @@ -385,7 +396,7 @@ def __atoms_from_formula_name(self): assert len(self.atoms) == 0 # At this point there must be only alpha-numeric - assert formula_name.isalnum(), 'fatal: formula name = %r'%formula_name + assert formula_name.isalnum(), "fatal: formula name = %r" % formula_name # Find the chemical element symbol in the general form: Xy, say He, Na, O, etc. upper_case_ids = list() @@ -403,17 +414,17 @@ def __atoms_from_formula_name(self): for i in upper_case_ids: num_atoms = 1 symbol = formula_name[i] - if i+1 in number_of_atoms_ids: - num_atoms = formula_name[i+1] - if i+1 in lower_case_ids: - symbol += formula_name[i+1] - if i+2 in number_of_atoms_ids: - num_atoms = formula_name[i+2] + if i + 1 in number_of_atoms_ids: + num_atoms = formula_name[i + 1] + if i + 1 in lower_case_ids: + symbol += formula_name[i + 1] + if i + 2 in number_of_atoms_ids: + num_atoms = formula_name[i + 2] if num_atoms == 1: self.atoms.append(symbol) else: - self.atoms.append(num_atoms+'*'+symbol) + self.atoms.append(num_atoms + "*" + symbol) def __latex_name_from_formula_name(self): """Try to build a LaTeX name from the empirical formula. @@ -440,146 +451,182 @@ def __latex_name_from_formula_name(self): formula_name = self.formula_name.strip() - latex_name = '{' + latex_name = "{" open_parenthesis = False open_parenthesis_after_numeric = False for idx, c_i in enumerate(formula_name): - - if c_i == '(': - latex_name += '}' + if c_i == "(": + latex_name += "}" open_parenthesis = True - if formula_name[idx-1].isnumeric(): - latex_name += r'_{\mathrm{(' # escape \ + if formula_name[idx - 1].isnumeric(): + latex_name += r"_{\mathrm{(" # escape \ open_parenthesis_after_numeric = True else: - latex_name += r'_\mathrm{(' # escape \ + latex_name += r"_\mathrm{(" # escape \ continue - if c_i == ')': + if c_i == ")": if open_parenthesis_after_numeric: - latex_name += ')}}' + latex_name += ")}}" open_parenthesis_after_numeric = False else: - latex_name += ')}' + latex_name += ")}" open_parenthesis = False continue - if c_i == '[': - latex_name += '[' # escape \ + if c_i == "[": + latex_name += "[" # escape \ continue - if c_i == ']': - latex_name += ']' + if c_i == "]": + latex_name += "]" continue if not open_parenthesis: - - if c_i == '*': - assert idx != 0, 'fatal: incorrect complexation on formula_name = %r'%formula_name - if formula_name[idx-1] == '^': - latex_name += r'^\bullet' # escape \ + if c_i == "*": + assert idx != 0, ( + "fatal: incorrect complexation on formula_name = %r" + % formula_name + ) + if formula_name[idx - 1] == "^": + latex_name += r"^\bullet" # escape \ else: - assert idx != len(formula_name)-1, 'fatal: incorrect complexation on formula_name = %r'%formula_name - latex_name += r'\bullet' # escape \ + assert idx != len(formula_name) - 1, ( + "fatal: incorrect complexation on formula_name = %r" + % formula_name + ) + latex_name += r"\bullet" # escape \ continue - if c_i == '^' and formula_name[idx-1] != '*': - assert idx < len(formula_name)-1, 'Error on %r'%formula_name - if formula_name[idx+1].isnumeric(): # there must be a charge sign - latex_name += '^{' + formula_name[idx+1:idx+3] + '}' - else: # no numeric, hence just a charge sign - latex_name += '^' + formula_name[idx+1] + if c_i == "^" and formula_name[idx - 1] != "*": + assert idx < len(formula_name) - 1, "Error on %r" % formula_name + if formula_name[idx + 1].isnumeric(): # there must be a charge sign + latex_name += "^{" + formula_name[idx + 1 : idx + 3] + "}" + else: # no numeric, hence just a charge sign + latex_name += "^" + formula_name[idx + 1] continue - if c_i.isalpha(): # alphabetic - latex_name += r'\mathrm{' + c_i + '}' + if c_i.isalpha(): # alphabetic + latex_name += r"\mathrm{" + c_i + "}" continue - if idx < len(formula_name)-1: # numeric + if idx < len(formula_name) - 1: # numeric # after letter and before of anything but ( - if c_i.isnumeric() and formula_name[idx-1].isalpha() and formula_name[idx+1] != '(': - latex_name += '_{' + c_i + '}' - elif c_i.isnumeric() and formula_name[idx-1].isalpha() and formula_name[idx+1] == '(': - latex_name += '_{' + c_i + '}' - elif c_i.isnumeric() and formula_name[idx-1] == ']': - latex_name += '_{' + c_i + '}' + if ( + c_i.isnumeric() + and formula_name[idx - 1].isalpha() + and formula_name[idx + 1] != "(" + ): + latex_name += "_{" + c_i + "}" + elif ( + c_i.isnumeric() + and formula_name[idx - 1].isalpha() + and formula_name[idx + 1] == "(" + ): + latex_name += "_{" + c_i + "}" + elif c_i.isnumeric() and formula_name[idx - 1] == "]": + latex_name += "_{" + c_i + "}" continue - if c_i.isnumeric(): # numeric - latex_name += '_{' + c_i + '}' + if c_i.isnumeric(): # numeric + latex_name += "_{" + c_i + "}" continue - else: # phase token + else: # phase token latex_name += c_i - self.latex_name = latex_name def __str__(self): - s = '\n\n\t ' + \ - '\n\t ' - return s % (self.name, - self.formula_name, - self.ordered_atoms_list(), - self.num_atoms, self.num_nuclide_types, self.molar_mass, self.molar_mass_unit, - self.charge, - self.phase, - self.flag, - self.info, - self.latex_name, - self.molar_radioactivity, self.molar_radioactivity_unit, - self.molar_heat_pwr, self.molar_heat_pwr_unit, - self.molar_gamma_pwr, self.molar_gamma_pwr_unit, - [i.split('*')[-1] for i in self.atoms], - ['%9.3e' % i for i in self.molar_radioactivity_fractions]) + s = ( + "\n\n\t " + + "\n\t " + ) + return s % ( + self.name, + self.formula_name, + self.ordered_atoms_list(), + self.num_atoms, + self.num_nuclide_types, + self.molar_mass, + self.molar_mass_unit, + self.charge, + self.phase, + self.flag, + self.info, + self.latex_name, + self.molar_radioactivity, + self.molar_radioactivity_unit, + self.molar_heat_pwr, + self.molar_heat_pwr_unit, + self.molar_gamma_pwr, + self.molar_gamma_pwr_unit, + [i.split("*")[-1] for i in self.atoms], + ["%9.3e" % i for i in self.molar_radioactivity_fractions], + ) def __repr__(self): - s = '\n\n\t ' - return s % (self.name, - self.formula_name, - self.ordered_atoms_list(), - self.num_atoms, self.num_nuclide_types, self.molar_mass, self.molar_mass_unit, - self.charge, - self.phase, - self.flag, - self.latex_name, - self.molar_radioactivity, self.molar_radioactivity_unit, - self.molar_heat_pwr, self.molar_heat_pwr_unit, - self.molar_gamma_pwr, self.molar_gamma_pwr_unit, - [i.split('*')[-1] for i in self.atoms], - ['%9.3e' % i for i in self.molar_radioactivity_fractions]) - -if __name__ == '__main__': - tbp_org = Species( name='TBP', formula_name='[C4H9O]_3PO(o)', - atoms=['12*C','27*H','4*O','P'] ) + s = ( + "\n\n\t " + ) + return s % ( + self.name, + self.formula_name, + self.ordered_atoms_list(), + self.num_atoms, + self.num_nuclide_types, + self.molar_mass, + self.molar_mass_unit, + self.charge, + self.phase, + self.flag, + self.latex_name, + self.molar_radioactivity, + self.molar_radioactivity_unit, + self.molar_heat_pwr, + self.molar_heat_pwr_unit, + self.molar_gamma_pwr, + self.molar_gamma_pwr_unit, + [i.split("*")[-1] for i in self.atoms], + ["%9.3e" % i for i in self.molar_radioactivity_fractions], + ) + + +if __name__ == "__main__": + tbp_org = Species( + name="TBP", formula_name="[C4H9O]_3PO(o)", atoms=["12*C", "27*H", "4*O", "P"] + ) print(tbp_org) - no3Minus_aqu = Species( name='NO3-', formula_name='NO3^-(a)', - atoms=['N','3*O'] ) + no3Minus_aqu = Species(name="NO3-", formula_name="NO3^-(a)", atoms=["N", "3*O"]) print(no3Minus_aqu) diff --git a/cortix/support/stream.py b/cortix/support/stream.py index ed088e93..65de7dc2 100644 --- a/cortix/support/stream.py +++ b/cortix/support/stream.py @@ -8,7 +8,7 @@ # # Licensed under the University of Massachusetts Lowell LICENSE: # https://github.com/dpploy/cortix/blob/master/LICENSE.txt -''' +""" Author: Valmor F. de Almeida dealmeidav@ornl.gov; vfda Stream container @@ -16,29 +16,24 @@ VFdALib support classes Sat Aug 15 17:24:02 EDT 2015 -''' -#********************************************************************************* +""" + +# ********************************************************************************* import os import sys import pandas from cortix.support.specie import Specie from cortix.support.quantity import Quantity -#********************************************************************************* - -class Stream: +# ********************************************************************************* -#********************************************************************************* -# Construction -#********************************************************************************* - def __init__(self, - timeStamp, - species=None, - quantities=None, - values=float(0.0) - ): +class Stream: + # ********************************************************************************* + # Construction + # ********************************************************************************* + def __init__(self, timeStamp, species=None, quantities=None, values=float(0.0)): # constructor # THIS WILL CREATE AN ORDERED "STREAM"; values must be in order! @@ -56,11 +51,11 @@ def __init__(self, if len(quantities) > 0: assert isinstance(quantities[-1], Quantity) -# assert type(value) == type(float()) + # assert type(value) == type(float()) -# List of quantities and species objects - self.species = species # list - self.quantities = quantities # list + # List of quantities and species objects + self.species = species # list + self.quantities = quantities # list names = list() # note order of names; values must be in the same order; caution @@ -70,8 +65,8 @@ def __init__(self, for quant in self.quantities: names.append(quant.name) -# ORDERED data; caution!! -# Table data stream + # ORDERED data; caution!! + # Table data stream self.stream = pandas.DataFrame(index=[timeStamp], columns=names) if isinstance(values, float): self.stream.fillna(values, inplace=True) @@ -79,39 +74,39 @@ def __init__(self, self.stream.fillna(0.0, inplace=True) if isinstance(values, list) and len(values) == len(names): - for (name, val) in zip(names, values): + for name, val in zip(names, values): self.stream.loc[timeStamp, name] = val return -#********************************************************************************* -# Public Member Functions -#********************************************************************************* + # ********************************************************************************* + # Public Member Functions + # ********************************************************************************* def GetTimeStamp(self): - ''' + """ Returns the time stamp of the stream. Returns ------- self.timeStamp: float - ''' + """ return self.timeStamp def GetActors(self): - ''' + """ Returns the actors present in the stream of data. Returns ------- list(self.stream.columns): list - ''' + """ return list(self.stream.columns) def GetSpecie(self, name): - ''' + """ Returns a specie named "name" from the stream. Parameters @@ -121,7 +116,7 @@ def GetSpecie(self, name): Returns ------- specie: obj - ''' + """ for specie in self.species: if specie.name == name: @@ -129,36 +124,36 @@ def GetSpecie(self, name): return None def GetSpecies(self): - ''' + """ Returns a list of all species in the stream. Returns ------- self.species: list - ''' + """ return self.species def GetQuantities(self): - ''' + """ Returns all the quantities given by the stream. Returns ------- self.quantities: list - ''' + """ return self.quantities def SetSpecieId(self, name, val): - ''' + """ Sets the numerical id of the specie of name "name" to val. Parameters ---------- name: str val: int - ''' + """ for specie in self.species: if specie.name == name: @@ -166,7 +161,7 @@ def SetSpecieId(self, name, val): return def GetQuantity(self, name): - ''' + """ Returns the specified quantity called "name" from the stream, or none if the specified name does not exist. @@ -177,7 +172,7 @@ def GetQuantity(self, name): Returns ------- quant: float - ''' + """ for quant in self.quantities: if quant.name == name: @@ -185,7 +180,7 @@ def GetQuantity(self, name): return None def GetRow(self, timeStamp=None): - ''' + """ Returns an entire row of data from the stream. A row of data is all the data in a dataframe at a specified time stamp, given by timeStamp. If timeStamp is not specified, this function will return the entire @@ -199,17 +194,16 @@ def GetRow(self, timeStamp=None): ------- self.stream.loc[self.timestamp, :]) or self.stream.loc[timeStamp, :]): list - ''' + """ if timeStamp is None: return list(self.stream.loc[self.timeStamp, :]) else: - assert timeStamp in self.stream.index, 'timeStamp = %r' % ( - timeStamp) + assert timeStamp in self.stream.index, "timeStamp = %r" % (timeStamp) return list(self.stream.loc[timeStamp, :]) def GetValue(self, actor, timeStamp=None): - ''' + """ Returns the value associated with a specified "actor" at a specified "timeStamp". If no timeStamp is specified, then the function will return all values associated with the specified actor at all time @@ -224,7 +218,7 @@ def GetValue(self, actor, timeStamp=None): ------- self.stream.loc[self.timeStamp, actor] or self.stream.loc[timeStamp, actor]: list or float, respectively. - ''' + """ assert actor in self.stream.columns if timeStamp is None: @@ -234,7 +228,7 @@ def GetValue(self, actor, timeStamp=None): return self.stream.loc[timeStamp, actor] def SetValue(self, actor, value=None, timeStamp=None): - ''' + """ Sets the value associated with a specified actor at a specified timeStamp to "value". If no value is specified, the value will default to 0.0. If no timeStamp is specified, it will set all values associated @@ -245,7 +239,7 @@ def SetValue(self, actor, value=None, timeStamp=None): actor: str value: float timeStamp: float - ''' + """ assert actor in self.stream.columns if timeStamp is None: @@ -254,13 +248,13 @@ def SetValue(self, actor, value=None, timeStamp=None): else: self.stream.loc[self.timeStamp, actor] = float(value) else: - assert timeStamp in self.stream.index, 'timeStamp = %r' % ( - timeStamp) + assert timeStamp in self.stream.index, "timeStamp = %r" % (timeStamp) if value is None: self.stream.loc[timeStamp, actor] = float(0.0) else: self.stream.loc[timeStamp, actor] = float(value) + # def __str__( self ): # s = ' %5s %5s %5s '+' molar mass: %6s '+' molar cc: %6s '+' mass cc: %6s '+' flag: %s '+'# atoms: %s'+' atoms: %s\n' # return s % (self.name, self.formulaName, self.phase, self.molarMass, self.molarCC, self.massCC, self.flag, self.nAtoms, self.atoms) @@ -269,8 +263,8 @@ def SetValue(self, actor, value=None, timeStamp=None): # s = ' %5s %5s %5s '+' molar mass: %6s '+' molar cc: %6s '+' mass cc: %6s '+' flag: %s '+'# atoms: %s'+' atoms: %s\n' # return s % (self.name, self.formulaName, self.phase, self.molarMass, self.molarCC, self.massCC, self.flag, self.nAtoms, self.atoms) -#********************************************************************************* +# ********************************************************************************* # Private helper functions (internal use: __) -#********************************************************************************* +# ********************************************************************************* -#============================= end class Stream ================================== +# ============================= end class Stream ================================== diff --git a/cortix/support/units.py b/cortix/support/units.py index 3fb6a5f3..65cac7e3 100644 --- a/cortix/support/units.py +++ b/cortix/support/units.py @@ -3,28 +3,28 @@ # This file is part of the Cortix toolkit environment. # https://cortix.org """Cortix module. - Convenient non-SI (and SI) units to be transformed to SI. +Convenient non-SI (and SI) units to be transformed to SI. - Usage - ----- +Usage +----- - On guest code.py: - from cortix import Units as unit +On guest code.py: +from cortix import Units as unit - examples: - size = 10*unit.meter - temp = 10*unit.kelvin +examples: +size = 10*unit.meter +temp = 10*unit.kelvin - temp = convert_temp(10, 'C', 'K') +temp = convert_temp(10, 'C', 'K') - incorrect: temp = 20*unit.F - correct: temp = unit.convert_temperature(20, 'F', 'K') +incorrect: temp = 20*unit.F +correct: temp = unit.convert_temperature(20, 'F', 'K') """ import scipy.constants as scipy_cte -class Units: +class Units: # make scipy a make constants available scipy_cte = scipy_cte @@ -50,29 +50,29 @@ class Units: # mass gram = scipy_cte.gram - kg = kilo*gram + kg = kilo * gram lb = scipy_cte.lb # length meter = 1.0 - cm = centi*meter + cm = centi * meter ft = scipy_cte.foot foot = ft inch = scipy_cte.inch # area - barn = 1.0e-28 * meter**2 # nuclear cross section + barn = 1.0e-28 * meter**2 # nuclear cross section # volume - cc = (centi*meter)**3 + cc = (centi * meter) ** 3 liter = scipy_cte.liter L = liter - mL = milli*L + mL = milli * L gallon = scipy_cte.gallon # energy/power/pressure joule = 1.0 - kj = kilo*joule + kj = kilo * joule watt = 1.0 btu = scipy_cte.Btu Btu = btu @@ -82,15 +82,19 @@ class Units: # charge/electric potential/current coulomb = 1.0 - volt = joule/coulomb - ampere = coulomb/second - var = volt*ampere # reactive electric power + volt = joule / coulomb + ampere = coulomb / second + var = volt * ampere # reactive electric power ppm = 1.0 # temperature difference - F = scipy_cte.convert_temperature(2,'F','K') - scipy_cte.convert_temperature(1,'F','K') - C = scipy_cte.convert_temperature(2,'C','K') - scipy_cte.convert_temperature(1,'C','K') + F = scipy_cte.convert_temperature(2, "F", "K") - scipy_cte.convert_temperature( + 1, "F", "K" + ) + C = scipy_cte.convert_temperature(2, "C", "K") - scipy_cte.convert_temperature( + 1, "C", "K" + ) K = 1.0 kelvin = 1.0 From cdb68ebc475132ef6fc5aa51116a065802c133b0 Mon Sep 17 00:00:00 2001 From: Uriel Acioli Date: Thu, 12 Sep 2024 09:25:00 -0300 Subject: [PATCH 9/9] refactor(cortix): switch from flat to src layout --- pyproject.toml | 2 +- {cortix => src/cortix}/__init__.py | 0 {cortix => src/cortix}/cortix_main.py | 0 {cortix => src/cortix}/module.py | 0 {cortix => src/cortix}/network.py | 0 {cortix => src/cortix}/node.py | 0 {cortix => src/cortix}/port.py | 0 {cortix => src/cortix}/support/chemeng/reaction_mechanism.py | 0 {cortix => src/cortix}/support/nuclear/actor.py | 0 {cortix => src/cortix}/support/nuclear/fuel_bucket.py | 0 {cortix => src/cortix}/support/nuclear/fuel_bundle.py | 0 {cortix => src/cortix}/support/nuclear/fuel_segment.py | 0 {cortix => src/cortix}/support/nuclear/fuelsegmentsgroups.py | 0 {cortix => src/cortix}/support/nuclear/fuelslug.py | 0 {cortix => src/cortix}/support/nuclear/nuclides.py | 0 {cortix => src/cortix}/support/nuclear/target_rod.py | 0 {cortix => src/cortix}/support/periodictable.py | 0 {cortix => src/cortix}/support/phase.py | 0 {cortix => src/cortix}/support/phase_new.py | 0 {cortix => src/cortix}/support/phase_newest.py | 0 {cortix => src/cortix}/support/quantity.py | 0 {cortix => src/cortix}/support/specie.py | 0 {cortix => src/cortix}/support/species.py | 0 {cortix => src/cortix}/support/stream.py | 0 {cortix => src/cortix}/support/units.py | 0 25 files changed, 1 insertion(+), 1 deletion(-) rename {cortix => src/cortix}/__init__.py (100%) rename {cortix => src/cortix}/cortix_main.py (100%) rename {cortix => src/cortix}/module.py (100%) rename {cortix => src/cortix}/network.py (100%) rename {cortix => src/cortix}/node.py (100%) rename {cortix => src/cortix}/port.py (100%) rename {cortix => src/cortix}/support/chemeng/reaction_mechanism.py (100%) rename {cortix => src/cortix}/support/nuclear/actor.py (100%) rename {cortix => src/cortix}/support/nuclear/fuel_bucket.py (100%) rename {cortix => src/cortix}/support/nuclear/fuel_bundle.py (100%) rename {cortix => src/cortix}/support/nuclear/fuel_segment.py (100%) rename {cortix => src/cortix}/support/nuclear/fuelsegmentsgroups.py (100%) rename {cortix => src/cortix}/support/nuclear/fuelslug.py (100%) rename {cortix => src/cortix}/support/nuclear/nuclides.py (100%) rename {cortix => src/cortix}/support/nuclear/target_rod.py (100%) rename {cortix => src/cortix}/support/periodictable.py (100%) rename {cortix => src/cortix}/support/phase.py (100%) rename {cortix => src/cortix}/support/phase_new.py (100%) rename {cortix => src/cortix}/support/phase_newest.py (100%) rename {cortix => src/cortix}/support/quantity.py (100%) rename {cortix => src/cortix}/support/specie.py (100%) rename {cortix => src/cortix}/support/species.py (100%) rename {cortix => src/cortix}/support/stream.py (100%) rename {cortix => src/cortix}/support/units.py (100%) diff --git a/pyproject.toml b/pyproject.toml index f83822a3..ef27452e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ classifiers = [ urls = { homepage = "https://cortix.org" } [tool.pdm] -distribution = false +distribution = true [tool.pdm.scripts] lint = { cmd = [ 'ruff', diff --git a/cortix/__init__.py b/src/cortix/__init__.py similarity index 100% rename from cortix/__init__.py rename to src/cortix/__init__.py diff --git a/cortix/cortix_main.py b/src/cortix/cortix_main.py similarity index 100% rename from cortix/cortix_main.py rename to src/cortix/cortix_main.py diff --git a/cortix/module.py b/src/cortix/module.py similarity index 100% rename from cortix/module.py rename to src/cortix/module.py diff --git a/cortix/network.py b/src/cortix/network.py similarity index 100% rename from cortix/network.py rename to src/cortix/network.py diff --git a/cortix/node.py b/src/cortix/node.py similarity index 100% rename from cortix/node.py rename to src/cortix/node.py diff --git a/cortix/port.py b/src/cortix/port.py similarity index 100% rename from cortix/port.py rename to src/cortix/port.py diff --git a/cortix/support/chemeng/reaction_mechanism.py b/src/cortix/support/chemeng/reaction_mechanism.py similarity index 100% rename from cortix/support/chemeng/reaction_mechanism.py rename to src/cortix/support/chemeng/reaction_mechanism.py diff --git a/cortix/support/nuclear/actor.py b/src/cortix/support/nuclear/actor.py similarity index 100% rename from cortix/support/nuclear/actor.py rename to src/cortix/support/nuclear/actor.py diff --git a/cortix/support/nuclear/fuel_bucket.py b/src/cortix/support/nuclear/fuel_bucket.py similarity index 100% rename from cortix/support/nuclear/fuel_bucket.py rename to src/cortix/support/nuclear/fuel_bucket.py diff --git a/cortix/support/nuclear/fuel_bundle.py b/src/cortix/support/nuclear/fuel_bundle.py similarity index 100% rename from cortix/support/nuclear/fuel_bundle.py rename to src/cortix/support/nuclear/fuel_bundle.py diff --git a/cortix/support/nuclear/fuel_segment.py b/src/cortix/support/nuclear/fuel_segment.py similarity index 100% rename from cortix/support/nuclear/fuel_segment.py rename to src/cortix/support/nuclear/fuel_segment.py diff --git a/cortix/support/nuclear/fuelsegmentsgroups.py b/src/cortix/support/nuclear/fuelsegmentsgroups.py similarity index 100% rename from cortix/support/nuclear/fuelsegmentsgroups.py rename to src/cortix/support/nuclear/fuelsegmentsgroups.py diff --git a/cortix/support/nuclear/fuelslug.py b/src/cortix/support/nuclear/fuelslug.py similarity index 100% rename from cortix/support/nuclear/fuelslug.py rename to src/cortix/support/nuclear/fuelslug.py diff --git a/cortix/support/nuclear/nuclides.py b/src/cortix/support/nuclear/nuclides.py similarity index 100% rename from cortix/support/nuclear/nuclides.py rename to src/cortix/support/nuclear/nuclides.py diff --git a/cortix/support/nuclear/target_rod.py b/src/cortix/support/nuclear/target_rod.py similarity index 100% rename from cortix/support/nuclear/target_rod.py rename to src/cortix/support/nuclear/target_rod.py diff --git a/cortix/support/periodictable.py b/src/cortix/support/periodictable.py similarity index 100% rename from cortix/support/periodictable.py rename to src/cortix/support/periodictable.py diff --git a/cortix/support/phase.py b/src/cortix/support/phase.py similarity index 100% rename from cortix/support/phase.py rename to src/cortix/support/phase.py diff --git a/cortix/support/phase_new.py b/src/cortix/support/phase_new.py similarity index 100% rename from cortix/support/phase_new.py rename to src/cortix/support/phase_new.py diff --git a/cortix/support/phase_newest.py b/src/cortix/support/phase_newest.py similarity index 100% rename from cortix/support/phase_newest.py rename to src/cortix/support/phase_newest.py diff --git a/cortix/support/quantity.py b/src/cortix/support/quantity.py similarity index 100% rename from cortix/support/quantity.py rename to src/cortix/support/quantity.py diff --git a/cortix/support/specie.py b/src/cortix/support/specie.py similarity index 100% rename from cortix/support/specie.py rename to src/cortix/support/specie.py diff --git a/cortix/support/species.py b/src/cortix/support/species.py similarity index 100% rename from cortix/support/species.py rename to src/cortix/support/species.py diff --git a/cortix/support/stream.py b/src/cortix/support/stream.py similarity index 100% rename from cortix/support/stream.py rename to src/cortix/support/stream.py diff --git a/cortix/support/units.py b/src/cortix/support/units.py similarity index 100% rename from cortix/support/units.py rename to src/cortix/support/units.py