From aa6dcee05e9f0835230c23a46f6a40653b18f254 Mon Sep 17 00:00:00 2001 From: haykh Date: Mon, 22 Dec 2025 15:51:45 -0500 Subject: [PATCH] spectra support --- README.md | 2 + dist/nt2py-1.3.0-py3-none-any.whl | Bin 0 -> 41964 bytes dist/nt2py-1.3.0.tar.gz | Bin 0 -> 35855 bytes nt2/__init__.py | 2 +- nt2/containers/data.py | 70 +++++++++++---- nt2/containers/particles.py | 43 +++++----- nt2/containers/spectra.py | 137 ++++++++++++++++++++++++++++++ nt2/readers/adios2.py | 18 ++++ nt2/readers/base.py | 30 +++++++ nt2/readers/hdf5.py | 83 ++++++++++++++---- pyproject.toml | 4 +- shell.nix | 2 +- 12 files changed, 332 insertions(+), 59 deletions(-) create mode 100644 dist/nt2py-1.3.0-py3-none-any.whl create mode 100644 dist/nt2py-1.3.0.tar.gz create mode 100644 nt2/containers/spectra.py diff --git a/README.md b/README.md index 289e774..3bbfa6a 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ data.spectra # < xr.Dataset > If using Jupyter notebook, you can quickly preview the loaded metadata by simply running a cell with just `data` in it (or in regular python, by doing `print(data)`). +> Note, that by default, the `hdf5` support is disabled in `nt2py` (i.e., only `ADIOS2` format is supported). To enable it, install the package as `pip install "nt2py[hdf5]"` instead of simply `pip install nt2py`. + #### Examples Plot a field (in cartesian space) at a specific time (or output step): diff --git a/dist/nt2py-1.3.0-py3-none-any.whl b/dist/nt2py-1.3.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..62f8ebdeef64213cbbca91cd76b968e253be767c GIT binary patch literal 41964 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 TLA^bi686!9l_K%ay8ZTlc?XIB literal 0 HcmV?d00001 diff --git a/dist/nt2py-1.3.0.tar.gz b/dist/nt2py-1.3.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..9470de846935df29ad4897eb073df5d6af48bf31 GIT binary patch literal 35855 zcmV(_K-9k(2-PuncejcBk&|ml||Lylbef+EbaQOK6ba+h92g8&8@vofz zuRg%fCNF{vQ2Eur=TA}$AHiT3IsKjoMc@NH{EN5FB8rPJ8^?>>nMaESeOQIrMM$5H zif~(Wes%xz%O8I^|G$0m-Se-Xy?o_I$(^>bfByGN^B)Dk{rvwBKT$G^H}lZ>=K0fS z-@Sa+5#PUm_T;N?pZTk~e6rrHv-F2>R`^A_itlI6zux>`@3QD(SW3m?vU_&N*MX;wT*cJO); z#CfPaeYRc4(JZQY`)cQHJw#3Fh^^ZH}n<$;RE~8p|Nd!{R7OvsDmBZ(&yi;7|NvX9ltl@c0bC zdf1c7+ZqnvN%=Nh1J8_s~H$psP_ z3ATVC!o@xZx~u|vinuT0sF*?S`UQ1+@mW>_qbtN(#muef~%g6*_%lJUT*Z0oTo_!JN|# z_`7o%y#Z9>D+DxvC!ckIHkZ+2cN7&zNH&}{$DKHxT_R@t(ofPV4(LBr% z1~LO;7F@7NfgM=~3CurMyiecK88e9Fg5TjG3M!xmmCu3|n^5>B%TvS`rwDv0=4>Fb zSg~ZYgirEyxP`BU5Vh+lj!AS57@#;Puxs-uqd5mMt%DT5*oj`jmJIlM8E*2aBGZuS z!kcw5#rb*}6>N-8;c*UgKgMn&6RZo|)Xl;?XEFnxY_f0~UIj4|Q^%e4j*D}c5maS2 z!~P+2LT`uoofJVDShzo`=l-Xi-e0==7BcM(+XQCH5oxS+oXOGJ- z2Aym@&uPvC{kQ|G%x29+>}P#qi9Fi!g$O<;5v)ro zTQC3`NVaf}>qr1olh#llrwp!@c@$oS_~6qHC^)mrBPRNQ4$w?-E+#pY2A*C;NiZjl z9iTLY#bGzgQ7ae_JVfZN5H!#`R`@Z^g5t;d3R&|pE;z7@h>AEwlAWSxhn6y!YDcp; z$a9c&3`e#;{0=5S2iPw}0$Il!3-(V3k9nyoZFq^{@2~`noFPt5*xG`1Gr0^EZy6al zlWb;3t7w*`M{H0~;%&0%s0dd$^FpD*fJ_5=9V1@I^9lUgp2gh!&b2I96@TI@He2LuRVErUmh0fSAz9>oF7KbT4&caF%$F#~x?1>VAhM*9Zh`ps9lE{ zyHM1D^*f=r*d6Tkv2(O?@U=sQ_&X5mu$N$u9>J#T5p;*_8KQ2qK6XGED*_moqs%F9 z=WWHIv+W|_1-L_*^+m*i=+PF!SDZV zO)x(j`wyP~E;r|o-lW;(0`$=Q(Ru@#Lca8O?_}`bC;v|dCnfoRI{frr{(qS92iZMx zUeHW**e)pSbkbzxOxBxxdDiLtke=rwn4)A#8POEaHc5VjWj5!Vq}UvxvIJ6u-sMHO z&PQBe966gj%tti&K`VO%JKx!5x+%W6I_4!DS{6p~o8!a_s8D2^P{Lq1z6=XzvlghL zNkUdK8zDuJE0tedozynO)lOU;BJ7NdFx^tC&$&39hilN*afcA*5(dy=3}oApH&;29cUzofh^31aom(aO=HakQuibg_1rja3Ez?}cbq-Fk(M(SrP%wfwrV zQ)-P60A4W3AX!G?I&zp8@nHj!6jP=gzygTu^uYPg|8#J|75U$wkjG2H?yKiKfZzF} zBDlyObW03%Of_zFtqFFh>NB#vM>&VJbZLDs2^z9eSl2EmFN1}$-`L&TNMAr zKVCeaK7oI){`{wB-#It`p39FR|C!qKNNuC;<`8h-`QKnz_WwB<9DjN*|3BFIUlyXF z1mqjDMXFQ4mDrs7?2p?kKPOH6h}qwYFr!1m&dt4E^-G-p=nFQTMoCmmr!e3jYX1+@ zy{!KYkB9gEKOf|0I_3J$bc$PeVg$Ftk4+rl2w&M3eKejqL5`27__@>RkZI5P3ch&a zsgK{fYyrll_kLUV3!49EfX(l6^6#Jjr^Df=W&7W!$M^I9gZwPAbmfG}W+lvj&)~C| z{%I~gWuf>4`wTp>k`e-s)kr3+rPN>+ZG5=Rq=@)>Q*5&Et8fj<2B{f;3$i3iE|j@V^}qk)f3U_=*tLXt6eJAF%NJ-VbYHHQVHVAT7-7xBh4WR& z4pc&vg1iHG>^FjaPu6vgKIgq+uwB=c6>u--kcegK6#k)Sizp6rAUu7!h^}zQ2M~R> z;eLTfj|hY9OONCarzh<3sBKpId)ORjiBXh3Go<9E=~W4pbRjX}&3+$5d02(>*C5`6 z&$2AdB&_GEt^gP1&W1U3`GO4e7dRulNHf@D18pbnA`0Vq?)DrPPf?>8kO99C17?AH z#>|dDC0=ibL{c!+9#FiW5;?}e*gjG4hcrrPbb7K#SKG?Ua^W`0i`+B7boDK!!v|^1 zHD}{Z^$DQ7KwMKaGv~!T%1W>+RU+K}`z!R6@Un&bzt^Jt%|HEr&abbpKfAesk3gP< zS^2C}0uzUcN62=a&z=6LiX6P-7JVu<>7UuK5ygDiHsrYD_FNxkWw#2Dj0(hdoXdQi z#g$@VTmuy*cPpqM3oJnMu%<5Zug0$4!sR}OI;W1DYk|^@?=s3(YPwI;O+0s!v~cEf zGHXDvA`=d;(m!riLGpbV%+Vjx10geV7I6ww;wRvTqUYqB#Uk3$ghlc4rAs0MzDarU zlzBfm0m|O<5JW@@pO*;tDC2;|JR=Lw%jXUVU4R|lM*MZxkX{PivV z`Y-(T3V*%8U(fK@|LAN8yqc{2;5^4M>^gsN2H~;EXq-70?lq3`u|HVcIM>+T%{8OR zjVsF|$sdgoMyFbpYW_o2;_D1Z=jZwGs~2e;WZpaSavv4j;ApoG)5?1!&P3p{}6}J*=w8+ilM&>T@tHxH44L7l^nC0gZ#7b2oP z>(WCMh4R68>xs-~WBQ@m~c|@&U*H^rSle_xt}3xBvHXyoCV(7NJfD zetGLGy`bZ}F?w@=4tp;x|2^hEa>IwS{|^Qy75~5C@bv!t?}PmCNl$LFJv8k1(CSB0 zq2g34B*c>h^gDFr5(YmNCID=MpV0QVjti0_pivTZAA|BT54XZ!z+)ee96<^9mSMae zyDvb6EtCZ;N){bp6X)TqqSdD~X;G>TlptaZa^K~2*p8I=lsKHX$ z;%HC10ENTFJNh)L%>bad!Yni|m9K~Q``K#;HkhR!FVl*ua?4i)Ae z|2x8_h-cuhVhr}u`Gan_Y|Hg8dF3dXhg)aGdlUe(Cr3$mK_1K4T6lq?@B*g5HE-{x zj^p`zqa$yYLE+&qi}WGVPt!HIS+qBCL(dT)O-tZR1Ipoa5FEe&z=$L4n+HCcH09$Q zMlJ*%c+hhmFzI8o+N~uC0y|X1=gahsCyy&eYJpM4bez2M3+tl@_BQ)B3*z`3l`Gq%>N6k>S)jl9NbAOL&PGbU{Q^c*>R zdJdmEXRZmStG=G~spL$)3@I!^kW+CHt6J*rC*oV_qJfZG#om zA33j&jyQ~ey3hQFzyH#I_)qtB^Wgu+k@C1ihTqtD;5X`oC@FfSM^!rgril}H!#zt$ zovva@KzmawqEf#n^o)1ml5)Sp*fwcj(s~Wc zRlYe#t9Yu6&)fIxgs_AvIid`QdRw^2v@n+rNb{psjG^5 zjXzcV-U}Y22p)zDQ5~g&_wXU&)pIa#Je`l-Bm`A2FWhc@d7`M{84=YC6{{Apdickv z#n+!-n|nL1J+hkoq)Dko`VUznX`aFM1LXXa0UYzSPVN7Id83QzCxF?b^Ujh#!#XbfJh%!0q_<#gxYrBjbg)h?(0xF9 zNXU7m_6y}Lz;|i!9EBPxh~b?3D7+BGN&mq7A7Y~)sj|*4EQFhb*_zmpv})@mE8M;% zo%ru#8&j_(8rztA`|l&=?R$5}fQO&|ot&KB=YRUx<1atHD+BBq|KaiDL3#W?9o)x% zez@~LX6YB^{imRfqZd_&9Rr|-ijGwn0Koh~Sg|kR^Y`>zdeP#er-EywbIc3ww<2CJ z4;4~xmT8PRGMpgUDIZSK4bgRl>B-^35t7JIgcnL5m$v?%W-ms}MjDp-yH&8(^)k`p z34T56A*8c2(9duO07?LD1j@E{K=3&r6|sb1(pT3Yf$+tVl}e6x7U#Q?&#u1Yp~u_0 zWV-Q79d`5{S8DSzP#@HGOcq9H<5L_28%!XS)0R?3bKg@{wK2d(H@L2+9ID*9p_-FB z9$Ad@o?NQKV&-=>(g|NC=4^;3Nd~EV#1dU!vP#$B6?o2Ff9a5-^orAE%n4L|jR=n! z;tZoN)>#TT6g$bIbm&Al7#3fSI)0}ipp$k&MccmmdgqjpTwq<-B*RuUsz%;uTYIc? z0gXQmM=)=L38zghEdALGd|u#RU!1$+5svfu*zt~g&R{6aU{#1B z-Cl?7^rX-b&pl>}=vIz<+4Bz9L}qC$EO>r!J`>RY6z=lBM&TQ!u22x0cwO?juZdE! z*JTQaC8UR`16Dubo)%BTK9_@wdYx`-)v8i%IbWQLa;yw?0V68vYOoZq)jD6ks8rEn zJ-Mld*ZP{7>TCt&o?4WZmIiBazV8-=xv9X2I3=?IfhMPOKsA`nru4;LOkyC4N(^kp zxkX15E(f&fMOT=GR1~Bys!y^|xMhKH!WZT;{2H~q{7EwZma23*Uu-iNj*AFJKbvyD z_9?&&^BnCZ)HS!;`NGOjT8u4JBY%H9NPHryOXB$RTmm)2>Grj+oKf20Aj((q5N{5^ zh34U*)Mz5Zt!Tj6pd|&Kv(gm=gthSYy(cc)hXwIfrE#iS(|avW`)jUYzfi_7J`1~Y z&-N{5eW8bG86rS+S#&scx&4?tYtR+qS*ircb)?Omh<7 zql!Sk-B$4{BI8}|5Wkh13vG+ov_rJ-+{7kP?$`7U0)}(4ld%W`tYDa7FDb*`<_0KS z)82*jEtu`DmW~qY4EWk5n z=7_cc-BE8|-}wDT3&hkLC)IQ**iaTKAQSOX&HZ2JL9#jXO+77SZ|j$@6Rb^nb16%>d6pA>%2Xh{bxf=z&x1Lo+o zN)e!T$kOKMjraQ3!XD!xM{?`!HQJNja9zJ zU__ecxN9n#pGXquA`-S`XXdeYIR<{eUtxvfMkoBQ!UbTPKjkpXG4j=+VBUe0{Es>1 zYQX+0Ea*(`CBCffj?NYaUS}WbU2~EKL%^DM9dk-JdS1Y|-wGid5@DgQg z(~fA?Qf%&Mpb0V~Cx_2&imWHD6>j%{D1GQ0^#IWL@Tld2~d+^U>Fgj~2OD$*>gjd4U?bBBA}z}W1oJF zX_|={;qQ&xhMkyBc_=%HV1~v*wAlWA;Er&-dM%0$vFNT<(PJ$7c2Ch84EOe!vkU(2 zwjszmE8^*on;tA ztAUq8(Nm++zyikhX)_&=x>L`$CLdpU;MF7_SY%? zx3Emu_AFw9ea#k_2igMj;OvGrG}2~Kvt6-oS)@6L+vcj)Ad_2}2T3!#x=gOXFzI5Z zdAy{1y|LEqsiV0L+ox}R{Tu6>bJ+M^c62+L!4g*bRMV(^ms(6Ca=)ouR5VWHJ7rZ( zaiNYur{p?>7QTwd5bn^&Y@5#Z@nd6G-(#d2)9EI*+M6wusYWq)ydUJWPZl z5(A4T6cA8*_>gQ?)7g5%Vq)tGJ$y*VKinl9qj7fpAi_)r#%74?K80QZm4hT&Q94e3 zJx>Ne=HZ+LsVz1MS=W`3_0V$)gfgP|9y)v1$Nz~04?~OQLZD-g(LGUe5r--&*+V)Z z1FV<395ZcF(l(yK3-Fz9W>gQ`@(tob%j|S5jxI4^fIPy3*u)}XWyo@mb*_*hb8(m; z&T1r?$f}QGLr|(m?*Y*$3mJqgNG?KY@JBZ*3;pzLXyuG(m8@vx%xLUb`c05KGaAJ? zur;0fEjDqyt4Wepwvk7?!*!-?Vtyu0rgJg}&Z`2BAdL00e&I6`R!!MdO3EGql*Z9~ zRi8pkYZE7qXV&xaRo>p{T@pU}_>-;GzE1L4HEUQ<>L38D0$!*ehP+9LX zS;3+XU1A!W`zM}N)VO)lD?(2*CAY(ino~a zS)AfgEF!QVW>T)`YHU_(yozSA^WwbVOE?|M{3M;x=(~OIGcyGMJxWL{^gHD&!SNJV zK5WHi_or%~JEwNVbB0M4-G3%OArb5)Y|YB5uzQZDH$6*Tss|+owuGm%$>?Z!*3n~t zk?vNZ*R;Iwde{f%vBF(%M*mFQx`Jclr@r9A{Yjff`G^tensBP{JX_ChpiDDB^5+P) z3Y^9#`T|M$M}V&`fmOV2*9LeV#zip2Lut=`IK)A*9m2#d+%vxFP6lVr(Y`VzlT_Dv z)JS6FyW>6)QFXr8<~&Liw|L(2iR>fUVj3y^M4-Yjc;6Dq~Ae z>{_-~EpP|X!_lrUSzXy$EutcByTvNnJ#0-CkQ*1(hC;SgwOvbjo0jtWmhO7hHJfg; z?3R$dUw8MeyMkixXW zW;_^?o)9fyNey<~kzYuRV9t)g z?K+%@HYfoN(-B8a9kH-z|4l}>vtR-MyvtEX@^er8XHnKKzX$X3no~l+?XfC zKxca3{B0RBk)b4{3N*mGiwI1Esord_87qa2>WP7q_LvS1tTMX^kB(JX?{D~zpdI5> zktf!gU}{7{XSu(I8SDaHLJ`4r-{Ap+jrH)8+uZ|&SpVfRSnDuaRbp$VQLEDs$zhG- zuuT|KJWpBRoe2bmpV56y2v(WfLX}=!OorBnHX$6Wj5wAIw}lsBmN^Y`E&qB<1haHS zBd6984R>3dM~-7=0Y-}g4d_8cQ9aR{>r~AHY#Zx3a#%1F~FsDA_gE$ejKXKgYDoR{rR*ygI>1}h~JrrcH-4D|6<~E5^f~^K8i17-E6%p>?Lz*W& z&_P+T8%`*rv?6q+JaO{K@mvfhW9tN3Tf107YyHOb0$`4ll;C-fv$I<=c2eYeu5<_H z+tMiXY+&{XLOxxa1YucA;@rw>o_Wme*HRi^nAErk3%>D{#Aw{u+UZie)7?i1u|AKv zIl%v>wx?{vVCdtzAkqXEbAK0`uog~b@6>?*s$%NY^m(JP>yFjISTSQ)-Udr5h239z z9{70?%r5J_sV9wY#84l{Xv%F3-d+!MlSg>B_dBkWwlt>SpglN81M7f)&q6o`XZr!p zF}d*AC8ygx?(loUUGNGVR5InIEF?W@>onjNY@>YKx4*BU5&5%~2i{&3Mdet?OnVU~ z3h+MbE70tI4EnR7eoU^rABsQUArIujDu|@wak?{T= z!zQ)x@t8H9K zr^Au4Aq)b)e2qPoryeiG?H{Aowqm-#TnygCrPF!}6vglb{D5w)uC`=RiIL=liFuVC zUB~y6Qy`LOb(yf(Y3YuPANa?7K@Bj2%_dZqI=qrC%b|OuMfF%*!^m-EpIuTfweO?K z7LtLs$02dIRbW<+K`Z)tzdSeg5;B*=5sMKnok_w(2 zQU&AyEw%!uM3%N4BQC0JSxeW>XdUYjC&Av!)d{a|>mzs#WCBg(mq;cy>#TIDDnoUY zqshktqtKT{dq=?|!;(r=M`8klp78+WqF6dZliqRKbC!`~uw!LEmO6hJ>%;N~sPZ!# z4C17=bCI{eN}3Ysc8#=}7m5mMT4+RVniY3E$MhtK4dz*= zd6=l7o(BDP#QX)#o;w}ny;HH}u(9cSg};{3-HsU>n)f};>RYkQv%7EPw*03LeTn@v z0chtwD~q&azMtQUGC)2aIQo)NDlxt-Xn-mscNdG>4M&%&>n%T1T}I% ze;D)fZI&CMG<&Rak5}#9)#8=!3!Y!Ne@QNr^i85&1c>aJk9b%Ue!1y6*NSs*+-|3> zw_}YaW1UwnY#m*@=FMz$3-?;R8u`Z;H}3vv*Q6TP=3me;YB3VE)8me&t(YVSP7g!U z*Gv#rX3E2hecyMvT-T1xaCVHHqht+2Zy|>wPD?`|ewI-g93Y^9Lv&z{n9yLl*rCak zJQL@dAa~kwHd$Im%EBZ z$t>RF(N#Dlh8&Sk5O;85CB&7H;Wn5R+RdkEJVB4102|}Rl8EO4rkCV61JI<`W)@=J zhEAs%NS=$!EEOS0tmk-Ifp8bFpm$2Zd zrH6@#u6BE)N!7BXVgic3mq)%+6+0bscV`v3G*0C)Fyw|=o*M3cyR%Nly!kK2X#Rc_@O*%5%9uIpuZMY3tl~>ss?gypD(5qk2ivk zbnHxdIQCqNAOn?n=q&qGbzhXei0X@_knojKUo3?f%|$8p&0b8nE*js>qC{J?+~Z&K z6%hlgAqdZn<}8C{*`ssHPFpF?bvg@XOWnM#hLaPpQ_{w{mR}#fN%WQF#;VRf=VQH6 z)z`oUF;Lz6SWceZynV@a+mb-b{UvWo8N#X@RK6du%06Bt%^3L&EDRRzbY z(q4CYy%u{y5iaZ*zODC&$CoN|jV8ZpG;xJg4L}xjsbIno+Cc#%e-*CMY&YHH!A0m1 z*fHtb04ZG4$F84mR$kY~K+;VSdJQ>)NRp_Ar3k5R!3{@4PFf_QB3TZCGVNKPti%2(}w@mr_3?v0(#J#}WmW_9vEdY%Ls@_bB#~w9JmHE3a9a^suqo zqx8xvI-}s&AbqdGO-vbqPnC3qq?cUY+J*|va2d#+|4?Bekf;KbyJEa{k z{|viwgz`2jd8tll^A7W6LU9CqotvoLMNiv{Xi;mGgALYBa}HF~#wD;V+f6IBIaW{^ zQqAvWvuNK6h>Ui!A~UQSUn@r0f!!zh?gWpN05u?qIPwVm}nv&BT?fFfcMibX^@#p%V_ zJxjbhOVlOue-GoS4<{eM%0M+~E&syfaj)pyxuR1sB<-;kQJpvqL=_67VB=X(FdXSg z+>KiqibvePn^XamhOj}u|7b&`sg%cxEF+7EIP_=FIPvJw`|YOP8oS%+v-t$^lIQH89&UeeqOpA7Sb z9;!%j1vDt+5?4+UNRe#T;p1v2vJUkp|1rx)h-KCa4JQxnNY3Ajp9{-hoL684p5hjB&C? z!f0(3MsyQPoZD@?T%_=<*JP6IbV@HLGsOoC59I1z>s9LjX-T(%HKDN`dV zBP`;H&=eNdIGvH<-}KYMR>jg~u38AjT)9+#ZaU?~97Q8FMhgKZU8E2&uPT7|lEx$p zUw;^)NQp^B>(fb5aaCzLj`HbtC9#I&UWq%b@&JFd(;nmTJWDaV163Y#Qgm%<3+D6W z0t8f}b(nxI>{@-y97^@i3*8)1;x)97c%X%JG1C!Kog9@2Ti#Yb%TSAm0we7wg8amQ z2`anejM&{N9$Ve_RmclU?iQ{c5Az4?EsojUgfBs>8WCoX6J5e@yp}Ao55R96*jnJl zWTX8h9Y)Y%%q$dxzTfXTL-^eVrC>dt4B%VOIfj`T&Bv1y_&gk*wMT}JS=4UO(oF%J z#ga3$9yi$PuhvYlY&jxw0F6rTi@Xt|@6linV*3b*<3uiT1ksrV3HpuBmH{PUE7A)} zhc@~U9U+zh?%C=kaJ+FBrk;4^mFJ&a6>7ih5{Tjjpvy~Zl9 zQJe&un5FH*i&8{p_V+~uDSQ~-6maz*EWOuf#Y)ax9P#+>UCS&yEM`wyo#Fww5BY(wDwGr`1l)~-_eM*CpMe9&I^ zW*_BmtBj~r(B;s0BkDc1)H#_Z|M76T=R722x@AvTb%TjG zIrle71RGCJ11lB!`NF@*(#`sO=aF-xAFS8$&J%d|m|OWgoKmTLObJYl18tnP_Tiv6 zq@Ks=MZU)Cqd{dl={bYG<_f=zax&vwqw@}w#qa3_Ok|}rZRw+#`m6$`EC3L3_@BV^7^Vyz5IbQ!Mmf8_aVzBDzT2NW zeNsc%BfO`;d1Sr^LR6YWTkkfX1#vibp=}uGo%T%=z3U~cfp;Kxx6^l*sQ9zHBRJdL zlr06!n5v&awWw`NpZs$X_fm|w+ud8&ejQwMJ9>LKRYBkKW|iniHKO%$QY&M~CKMW5 zw@Dq3u*#UEmX~F1%e4Xd=I&=q;@}3=0d4oe5*N2gX(c@fb=y4z`JJ z&5}qa_DOYrIh+Ijn79&)(O-tATpfpV(OrooUnM6dcx_z%-gExJz^uZ2d67k>gnxX| zWbN{0lYgOwvnNXI^b$L*ge76|dWJW7!@{-(E=e9Zh5Yu4LvK2{-*<+Y*LsXcWCY%4KIx4DTz*=#JAQ5bPe%)VzVTkZ4x zOB8=;DgPzn?Xvz`?6WQI0FZ-Fy7jGkFW{74C|l>!#rMC}D+cSY4p1Cm-X5T?qib=2 zfUUvWI|TF=Rj^St#-P&cgKiK|t-#1T90IPGL9!E9@fPB~L0d9^8DgIRj(A5Db4wt7 zxi%cbD5~&{MbMvtw0cV66XITY7C}T8)$_D)rD6LR4<_*x&P3H+!I=`>+{q%9&2t<`}`CRXk4G zPMp2-qkE7fZcU5=pip2)e6FVDJ~`pxQ2ZII1u0T_DTULfHO zdp~v$kxB}KIyr%U3?*6d{HwpaEJ=dp<@ewH=>S<`CZMmF*jVU2bQhN!ui8COiEVv3 z>*~bL*Hgr88qK#gWG3i8wg7AqoJ5Q>ONV%~>83DSp5$xIGNyN%MgE8j8x+=OU0Ygx zxv{%_YJGXOD|fE%CC$E?jkn0OuPPsuY#)?}r|E{6h9}tXQTU?Bv@g7tOdN`ASBsRW zQ8V=pzNeB+opLbt=4uHu8aMB>z7y!yTbG^K{ak5tW8zHoEr~S z=Pb>>ViD#onr(ASOQ*FurS5|*3$NM_mc)PK8xafYTS8mU@Z%w7Wz5$HXHhV1+FpOJmBTcr-( zY9**LU0|pmBbQg&ky$lVu7B96(Mrj%?-E)o3H8c>4Wol-wRg2@m@?eGDG8EJ=P+4f zpP>zdR3H4*PEgG}YlxhZH=-?!F> zp{ku_uP%C`WJ%r|Mvn!N_kS_>+~8er|Jrx|*I@9t ze^S2x>-f{*{rz7b@BT02)-K})FXnrLw|52N`BpyLX4cEOZgIDktisl`+{fq@z3BJ! zT-|@gc=a%63w9L8+1%!9@dfY(%v3Ge)))F^W#yA6;m_{jddjarW%!RYoO{ymeg7?~ zbR@?vMHQ9Idr`HsvRBK91WG^ybtEoSlLuRyoW5o33^H{ntX%>E$$hW`2zHZ#d@@7K ze#;KW;T?DLV2J8c7%||VThqC;yA7fM-rrp!AtRqaVCf1n+d*&b{;fseP#r*b_W>#wuSqPn3AUR z3xC?i^`+WWt>cSoOS^5iHpONM@Ihz(qNT%5{7WtFK};c}dSjraJqC$UC;b{x>DcAo zp0;%!`fHd)i``3T&Dh9Ud5K>$UvuuxP958w$Vl$&mSjjU71^F|wa=`@x~F5SA7eEk z0GlAD%abb4`=^)cYBwP8bK3kny;I?NluU)mCVzIWhnmUTaoIJCHsp925u3#Usgxlm zGFn&gJkUZzby)apyN;t-RK&Z(NNK^S=U6WKRk(<3&TS6QYKBjply!}D;~l{wTMC{w zMl7!*-C?wngI_}-b-WfW7ThlwKYF3*c#O^wYd^|oahiu76`l;voFi5b_@&1`9cfRG zh8W*er15wV9t}&YKxjtSgj#q_Xo;_>%60)jtNKu{is4oOykmG>;Dy&1AoTDd&%x(D zb4RS!jZu(Ydd-T;o3e47Mm)_0n<(($tb1c~S&(;dH@GWkC;Ev0-CX!CCdSY6()a2L zUKdYSP10ao`Q54xn>_mU;_d1Q%*Im7YF7=f94DjI-r|Q{bpOk{#J=Sv^^#dX{6+P0 z!@y(q_m#U{>!jA=K)wns*yo|CQcbB1&q4}1{4MKoqY4_6!hk?Bp>H<<5Y ziqD5(9`Q0f>kRT0J^zDzP6y0pVdn@fCf#m}UXiU{uqkS13kQe)uE!^K0*RE*De~xTIR30F$_RPpX_jz3XUNzi1s?^QgEpk!op$xtMYt_^kt2JVx3CWl zv$1whTL41j4HI-29PQEfyx3tbmZQ&Ia$--ju@H`I0?{+H4qm$5o)f?-G z_>f3_e^6s3m<-9|l{{TxN2R`;)ou3oP|1GR1yQcoI#7$K@p&cFDi8yQzS>df;IIl^ z?^@Mf2V4w1td$`)t`(wbM_2G}7UI^w^s+0bok!e%ZX&VouAeIA9Q8dOx=O?XTSg7>Ep+x z{r~Z&pZ4$f{~yo)uP2l6af`qzxC|)`U;ZSSe+zqJK9%HoC*x-19+kk4J>$2cQ_LtC z?YXLlyGQa-GScvsQt*|0Ym&`sI$LjWePPA?b-MOk@z~gDV!FOqyP{5Yp(H;6=P~{= zrz!I|wp?bjptGHYdrf}vkH~2zFX>6xw=r2u%==e3o@4Q$A_Fu9BSRH_;WCt+Jh%$y zsQH`CDk;dadW)-Ob?UG;%%`6!`>XYdnRpUuqm8PHynu@3u0AUDHN6V6H<$*)i*r*9>ciiwOQs~qLfAU0Kh7)*LC=^w`DQi?^L(+1+oVKI2?!SC)}yQZ+4tza=Y-vdP-e$0%t{? zrYXx>aW*+lT=Xt`&49m#=iY#>Y#YiR8xSW&JKP%>0^ZDbX%T9TbfhP&?)S|K4dSudiu4hCfHM?_D1*+AkNeD6N+*qM9=doWH^ob7*wi_;ZGK@}7A84fdgZQ@ z3$in;FwYh}0u8)0Tg41@gCtsMazU^jUlJ7M_`V1cngaU3PKl5?N_K$6Fj$>5rUz8% zLC<+W{dmy5`H#M>80#IBJMP}heOn3HM+64Fd?3=4^IaPsJR{jo6^tvXE@a-Stb}DpRHH3bRL#23*tC2I)FTKCV&3ZSv9$wTn#Ma3(s%9BfA{z+xIlm!YbqMWlohfoK-y zBBlf96z`AFUf6t;HJ<`X{K)NGWrUP)_!%5^AXl~M#BK&NZiYv8aM-ehsz{9d2^mG_{H5=3NzvbSD@_T z#H9LrR6U$l8bilCohFkFITr@CLjpZWLX{a%ak6L?6>+^$`lu9h*)nv3gXUrzLok?l zDUbgl+T;p45h-HnDy5)h^p-J-7+gNTxvW5(@*d6S*Ts+Xl@4JZTozNaOri< zgRHfl$ptF5Fh$gR{J;pU&7d=}mF3Zcp4yQP9&z!cWbzJCWQm#>6#OZku6HT5Z^OrEiw95+AL$8P`cc`jWz!Hg5NqQD7_@zg6 zsm{q&t7wx&ft6(E;N6l<0oFc1cM)B9s0<^9H;=9X`Tptr#z)P(T7kLRx7e~$x-;Mc z{DV9RR1NLBWSR&g=Q_NxZ-x0nEFAuY1gX$Qv%9R;K70ag599SY&#ZBCM6X`JCr^~- zZ+}B-97)f48s^D^0=hsqg|m&G6BQDJ6gh%?W_>D?dsI;~0;G5y#$`BKUMAx*^OL5sZzmPQq)My#?*e$GQnnp&Iu_x>rlWCCel9{LR9>8GwAc3OxM17I> zjFcW!HCaKf>S1Y^J05QbTKxt1f8kLTuJ?Cf0B*Mbo;)5NSN;D6_x9hPhy8b-EU&%? zp$FR+S`OGpYoNG{@ZJ;=UMtIrpx*R6AAa>BjRUQW;7XsJN~i^IQ1a{8086tU%&LnIhRL|Hw!R9m3l<^eL$;IlaP8eyrS02N5z4GkgLm1a-^{O0P zed)s02_Ja>Yx=|chie?(C3JRX-l;024iRz$+iWcOY_N6KUEH^m!J#yXr4d? z=OUlL^7+GAHN&*#i9|S5OZ=dfsNR*1hrSV>M-!nxaenAJe-JEdNmY#i(eu>1Ktf@m zuDE0!JJKu7FL8Nd=>P$tPywvVr$?$IdJ<(nYMvWT|OD=A)BI-(iu2egwcG0~+YX(veV9YXr~2^qv{`sRJg9_Bv0 zj;Cnbzl@69mn0s@^(aF3Uy#r*QH1;w#TKLE&qID;TN86aPs;&j$4HJ=hXr0I7WCHQE+2(L;@0u4PJST97wm!C@^VfEKM zhkal*+i8M{mydx3e?VTWzD6rA`BO4}J#q%{FX~d^s;|*jTRM?kHQ~{W{)vR$5UW>JF zk)ombX#K`vIHd1Wjwkt5!4&qQ6&{6b$MCOfU7&HIQ{QI|1fUx%`9*s=Ao~I=tW9+q zJQN=}<^of9m!$NJsVk6QM(b&1*{&;{Ch@L)X?=}Uy9`(@Yip3H@USXmqdu?>DnvpF zFlgh3KW>89)mHNgnJX!FRJtm(q>@b)j|Ks&H~h>-=z6&mpxKul+sk+n3O+`qG8euT zT`gVHpQvM{tWU;LII8Y?O{TCI;nj%(l@peIdCKBN!sH$}FsDShNP0zdedjB7AG>@c=$=(4c?^YVC-0@cYn z#;xIW{Nc@lMBcHLg%{!0?DFJHLqMArw?Tkt??Gex8q`5OUpREnPmfMS1b=0`3qaLP z5~IOhq`0GLJkC!UrcPw+6d*{^j@X350vB%D()on-VZASO5o%uQ% zIm3S6xzU={1VVGWb{`=+vtfsmdRsSsxh_jtM`U-n2Lp4ZDrW5XjxCDF**-hnp}bju z4-O?i-S1e1(mT>iI_y-sU=e4IF3)OOl&5LfEnba|g`77La1t87&qF6npz&Ea_nkir zUni^qO<#%Lt7E~}3WL`?A%QqvqLoj_`~ivB2M1)fdzXjos}_5U-R`1~%)q`2nTiZttS!WHI+*WCon30AJ`s

ouQSOpf~NwlilHC97cDG;tc{hO*6(we2T96t0s{6}_5Tp{vI?hGzeKvN zR+JhT+iFnQk!nzKaK`c3rv=l%9K%SQ?pIVWEM&_|BZIy*I$#p)D;ssOjPnPl z?+EdQ!{v_Z?$W_LO4MaoE)ejjw&$GvWCd%%`q2=43?PmWRO66UM!hVFSV8mT!=i_p zM<5l!)8cSs-=W98V~#cl8?BBqYL6ETk0hlUkaqg(iUMCgv0H_I*Mne4Ddr|KsmiQgh!4yz=KrnS+}dD>tZM0RhSf0 z$3Z?~EgQGSbH5GDU~}yEJEfK1#y(vCVBAxu;n)n2l~uWBq^T+`s%Cu+hBSdD#vHc^ zaH(OcYN)ZyyLP!+YW?9u4gN%dIJ;?ZUocX_qupc559ddY@W~()Sfs+JV^=76oiQG1 zlV{l&9;-`Ts8F4&nYW9h)!1CI%|Y)8mp`7B-Qu!ZoeX31tGQSUCxo%=aVg9nIdsfi z6wMdNlw;IZuDAN);OhW$WUfJRnV2tJ}#eq%0&<9f5_4)zk4R45-$&i2_9&<&Hz ziX%t)V;-SD=I7kw?_eCa`*7SIgd=Y*4lht5$LVbBo`*RlwGjEh+hdJWBG- zn)zTZT$IVzw6yDqI4P+hfZB~It)In5#Hu3=|CM#5dS{K>>QKX0I@Iv?I@A`^kqt%C zRczO$Ux{-}@-xaQN`ZYWmo2BG{duWrc7s-VVXkT<1H;IbmLe{w*^+pZRq5qqJ2>N0 zhi!iG@|W^++(36xd9m-Dx5F9`LlZ>9URol>K1_5=o5h`0_FlgpW{*O~345m+8Hf1+ zRI7+BG3?`+_hLUjBBXAb8XI{97vQb!YoNG7l@a^JZl1zhf%(YW-Y*c z*#zdx?92aZ0pq`$V9e4q2I8*V_ig2=H)d4qTC3H&933v)ugTfw8c@2SW+{)dW_5Af z97pW2v>j?~JH+B4T9}$u%)-K z!7%T=Y1odLNPXn3nTZT;jc&17E8*lODa>O!r%=t=8Cxb-KQXIyh zS{!kh;sriN4mYhHA!+4qQ5BChT+CS&WbR&aP8i-uR{YSWIN;5STTG86Kutu_i+YwW8L5$b5FTL^K&`#gz>#Q#IC8Dd|0cco4xZ6e9V^K+t}}#}JCW~J_5A=+7t1Wn zm$u=OFEM43>O=Pyop`xhiGI{lOEx!_pO_)O+-tPSCHX9f!|`CBE5|Kt89>0jI%Ub` zfyMka^;OtDD2hi!BJevU8jK)zaceiLU?hZyGGKu;70}iG^I!wL|1559FFkM@ibZL! zOuJs%@ESX0eM@h4jB4&MF}06Xr&-6jDpqP$X9Id>{wx3O~ zam{)s7O6pj6ySX1O&?(|>Ep&Kq1FrIIW5F}&i#q$_gp#1@5D3XspcYgP~-&vF} z4InDnm|Z))81y|^vy1zRfxRLk-+%Z}0jhz2-$x(aBd!Qe73kAhl8FZTaP`VCxsl zBb~P!ug8>(ezfOM+bC@5`M#=~)p*eFHx~+%`8oxFeA%ik+t8MN4MAgDN3BP%!k}#{ zus(l~E+015w-`P{9=_vHciXxJ-^<-Y+$5Wq#D+ys%$HWe^IwthVtvAuDZsRLb|s|c zDUS8fh>mD5*ErgqsxGFVIG#)SU^T^0UJkKvoyl+i`On!rEkLjP-|Hh;=;pU)Znsmj zxv3tAw#l@i?D%8TczW2usEA-5%k9OT9h}s@&NBvprMA zSUMHI*l1_gFyq{!YH9;~ixby@-?tvP{_6hc(z^ClXZS=PuoA+O}%A=etZS7D}-PfLFV=RxtnH`DC4%S%X5 z2?IRVHm z2SdRy^Ryd`b!5q~QHgk`SG;kSVOd<*62)3>b4!K-&H3iGxeBNuN)duR^X7WO0xjW0 zC7Hy#H_Ze6_qVMNy~D=7rp>=CLw4PAN{RMa4I^O!Haz@_rO-B7WQno-3NJatOznF{ zS-f-lYh));3Bv`LeMuAysX@H*&X?Zck4jZ88!h{MP)-t=DgvA1Vzts6HLG5~eytp; zST1{#8iI~pYspfI2Fy6XowJ*=4pd#h^yK)ME1IAzG2ebrn!GjM^Y$FI#&M18IuV?L7b)+AjI+VWlG$HzQeSKd6PO< zK^9>~=v<`eGw-!(zh~OkY?wp?DoYgUs7L7SB9;0P4y>lo?+yaCs{q>=(1;-bq8j0b z{DJ3aww_;vc?nnTo+KC#hd`c(#hWlBzgd>)D%c`jx1^MBKqdE0Ub{UGyF+oPt7#Y-#; zsvhGf9+|JP9bUZTm*((oTieOjcCA_yVxj%!S8W>Amg)gKrc*%B!n#+r#!r4#`(delzaY z(&d?afOl-oK`W9A({}t|`vA`}9&GS0vA|eAHty4$WWUk#s#4(SwCCXeeQndD%j|%t z=+LgH#% z)898@Huy@dNEH-nEe<`F_6Yfe07-*_$uI6Lj0xrGUNIXmq1t$`MgXQIHv_6ic{JE3 z%#*j8(+fTrqt+zW#z?~%E zt~CL7w;tu*5a8!#@are~J$ zXgQ>PiiGpn_sL*%=6u3mPDV!q%}sk(s;q8dl_5mG|G-y^lV{<* zo`swxuZE=XU-dez{bMXQ)$8b>jfS1qd)vwjerL??XJBlo-~ThG{P%zUd(uA{Jib5w z{kZ#oeXn10B;4x8Hg>1;R}pYe_!2cK0HO?|=BY1N%-HqAbWVrq;Vy8)nUfMW7q>0X z2_9aGT4d4rM!T@J`EuoiF7s`38!m5`ZuiBejl|i&wplxxX1!_PpnA7 zU4pCj9|+9M&pmiFc)aIEUA{k5>hxehq)6l}} zjBv5()mARmq;G&x&%pf?XNX{q1}y-2cp(jufP<^xW5VGX`}idKHcisIoklAT@uN

22D6}8~ip`%= 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 str: cntr = 0 for l_ in lst: if cntr > 5: - c += "\n " + c += "\n| " cntr = 0 c += f"{l_}, " cntr += 1 @@ -291,34 +292,67 @@ def compactify(lst: list[Any] | KeysView[Any]) -> str: string = "" if self.fields_defined: - string += "Fields:\n" - string += f" - coordinates: {self.coordinate_system.value}\n" - string += f" - data axes: {compactify(self.fields.indexes.keys())}\n" + string += "FieldsDataset:\n" + string += "==============\n" + string += f"| Coordinates:\n| {self.coordinate_system.value}\n|\n" + string += f"| Data axes:\n| {compactify(self.fields.indexes.keys())}\n|\n" delta_t = ( self.fields.coords["t"].values[1] - self.fields.coords["t"].values[0] ) / (self.fields.coords["s"].values[1] - self.fields.coords["s"].values[0]) - string += f" - dt: {delta_t:.2e}\n" + string += f"| - dt: {delta_t:.2e}\n" for key in self.fields.coords.keys(): crd = self.fields.coords[key].values fmt = "" if key != "s": fmt = ".2f" - string += f" - {key}: {crd.min():{fmt}} -> {crd.max():{fmt}} [{len(crd)}]\n" - string += ( - f" - quantities: {compactify(sorted(self.fields.data_vars.keys()))}\n" - ) - string += f" - total size: {ToHumanReadable(self.fields.nbytes)}\n\n" + string += f"| - {key}: {crd.min():{fmt}} -> {crd.max():{fmt}} [{len(crd)}]\n" + string += "|\n" + string += f"| Quantities:\n| {compactify(sorted(self.fields.data_vars.keys()))}\n|\n" + string += f"| Total size: {ToHumanReadable(self.fields.nbytes)}\n\n" else: - string += "Fields: empty\n\n" + string += "FieldsDataset:\n" + string += "==============\n" + string += " empty\n\n" if self.particles_defined and self.particles is not None: species = sorted(self.particles.species) - string += "Particle species:\n" - string += f" - species: {compactify(species)}\n" - string += f" - timesteps: {len(self.particles.times)}\n" - string += f" - quantities: {compactify(self.particles.columns)}\n" - string += f" - total size: {ToHumanReadable(self.particles.nbytes)}\n\n" + string += "ParticleDataset:\n" + string += "================\n" + string += f"| Species:\n| {compactify(species)}\n|\n" + string += f"| Timesteps:\n| {len(self.particles.times)}\n|\n" + string += f"| Quantities:\n| {compactify(self.particles.columns)}\n|\n" + string += f"| Total size: {ToHumanReadable(self.particles.nbytes)}\n|\n" + string += self.help_particles("| ") + string += "\n" + else: + string += "ParticleDataset:\n" + string += "================\n" + string += " empty\n\n" + if self.spectra_defined and self.spectra is not None: + string += "SpectraDataset:\n" + string += "===============\n" + string += ( + f"| Data axes:\n| {compactify(self.spectra.indexes.keys())}\n|\n" + ) + delta_t = ( + self.spectra.coords["t"].values[1] - self.spectra.coords["t"].values[0] + ) / ( + self.spectra.coords["s"].values[1] - self.spectra.coords["s"].values[0] + ) + string += f"| - dt: {delta_t:.2e}\n" + for key in self.spectra.coords.keys(): + crd = self.spectra.coords[key].values + fmt = "" + if key != "s": + fmt = ".2f" + string += f"| - {key}: {crd.min():{fmt}} -> {crd.max():{fmt}} [{len(crd)}]\n" + string += "|\n" + string += f"| Quantities:\n| {compactify(sorted(self.spectra.data_vars.keys()))}\n|\n" + string += f"| Total size: {ToHumanReadable(self.spectra.nbytes)}\n|\n" + string += self.help_spectra("| ") else: - string += "Particles: empty\n\n" + string += "SpectraDataset:\n" + string += "===============\n" + string += " empty\n\n" return string diff --git a/nt2/containers/particles.py b/nt2/containers/particles.py index 5a01dfb..e7d4f0e 100644 --- a/nt2/containers/particles.py +++ b/nt2/containers/particles.py @@ -384,26 +384,26 @@ def _attach_columns(part: pd.DataFrame) -> pd.DataFrame: .drop(columns=["row"]) ) - def help(self) -> str: - ret = "- use .sel(...) to select particles based on criteria:\n" - ret += " t : time (float)\n" - ret += " st : step (int)\n" - ret += " sp : species (int)\n" - ret += " id : particle id (int)\n\n" - ret += " # example:\n" - ret += " # .sel(t=slice(10.0, 20.0), sp=[1, 2, 3], id=[42, 22])\n\n" - ret += "- use .isel(...) to select particles based on output step:\n" - ret += " t : timestamp index (int)\n" - ret += " st : step index (int)\n\n" - ret += " # example:\n" - ret += " # .isel(t=-1)\n" - ret += "\n" - ret += "- .sel and .isel can be chained together:\n\n" - ret += " # example:\n" - ret += " # .isel(t=-1).sel(sp=1).sel(id=[55, 66])\n\n" - ret += "- use .load(cols=[...]) to load data into a pandas DataFrame (`cols` defaults to all columns)\n\n" - ret += " # example:\n" - ret += " # .sel(...).load()\n" + def help(self, prepend="") -> str: + ret = f"{prepend}- use .sel(...) to select particles based on criteria:\n" + ret += f"{prepend} t : time (float)\n" + ret += f"{prepend} st : step (int)\n" + ret += f"{prepend} sp : species (int)\n" + ret += f"{prepend} id : particle id (int)\n{prepend}\n" + ret += f"{prepend} # example:\n" + ret += f"{prepend} # .sel(t=slice(10.0, 20.0), sp=[1, 2, 3], id=[42, 22])\n{prepend}\n" + ret += f"{prepend}- use .isel(...) to select particles based on output step:\n" + ret += f"{prepend} t : timestamp index (int)\n" + ret += f"{prepend} st : step index (int)\n{prepend}\n" + ret += f"{prepend} # example:\n" + ret += f"{prepend} # .isel(t=-1)\n" + ret += f"{prepend}\n" + ret += f"{prepend}- .sel and .isel can be chained together:\n{prepend}\n" + ret += f"{prepend} # example:\n" + ret += f"{prepend} # .isel(t=-1).sel(sp=1).sel(id=[55, 66])\n{prepend}\n" + ret += f"{prepend}- use .load(cols=[...]) to load data into a pandas DataFrame (`cols` defaults to all columns)\n{prepend}\n" + ret += f"{prepend} # example:\n" + ret += f"{prepend} # .sel(...).load()\n" return ret def __repr__(self) -> str: @@ -726,3 +726,6 @@ def get_quantity_for_species(sp: int) -> np.ndarray: read_colnames=ReadColnames, read_column=ReadColumn, ) + + def help_particles(self, prepend="") -> str: + return self.particles.help(prepend) if self.particles is not None else "" diff --git a/nt2/containers/spectra.py b/nt2/containers/spectra.py new file mode 100644 index 0000000..7288b42 --- /dev/null +++ b/nt2/containers/spectra.py @@ -0,0 +1,137 @@ +from typing import Any + +import dask +import dask.array as da +import xarray as xr +import numpy as np + +from nt2.containers.container import BaseContainer +from nt2.readers.base import BaseReader + + +class Spectra(BaseContainer): + """Parent class to manager the spectra dataframe.""" + + @staticmethod + @dask.delayed + def __read_spectrum(path: str, reader: BaseReader, spectrum: str, step: int) -> Any: + """Reads a spectrum from the data. + + This is a dask-delayed function used further to build the dataset. + + Parameters + ---------- + path : str + Main path to the data. + reader : BaseReader + Reader to use to read the data. + spectrum : str + Spectrum array to read. + step : int + Step to read. + + Returns + ------- + Any + Spectrum data. + + """ + return reader.ReadArrayAtTimestep(path, "spectra", spectrum, step) + + def __init__(self, **kwargs: Any) -> None: + super(Spectra, self).__init__(**kwargs) + if self.reader.DefinesCategory(self.path, "spectra"): + self.__spectra_defined = True + self.__spectra = self.__read_spectra() + else: + self.__spectra_defined = False + self.__spectra = xr.Dataset() + + @property + def spectra_defined(self) -> bool: + """bool: Whether the spectra category is defined.""" + return self.__spectra_defined + + @property + def spectra(self) -> xr.Dataset: + """xr.Dataset: The spectra dataframe.""" + return self.__spectra + + def __read_spectra(self) -> xr.Dataset: + self.reader.VerifySameCategoryNames(self.path, "spectra", "s") + valid_steps = sorted(self.reader.GetValidSteps(self.path, "spectra")) + spectra_names = self.reader.ReadCategoryNamesAtTimestep( + self.path, "spectra", "s", valid_steps[0] + ) + spectra_names = set(s for s in sorted(spectra_names) if s.startswith("sN")) + ebin_name = "sEbn" + first_step = valid_steps[0] + first_spectrum_name = next(iter(spectra_names)) + shape = self.reader.ReadArrayShapeExplicitlyAtTimestep( + self.path, "spectra", first_spectrum_name, first_step + ) + times = self.reader.ReadPerTimestepVariable(self.path, "spectra", "Time", "t") + steps = self.reader.ReadPerTimestepVariable(self.path, "spectra", "Step", "s") + + ebins = self.reader.ReadArrayAtTimestep( + self.path, "spectra", ebin_name, first_step + ) + + diffs = np.diff(ebins) + if np.isclose(diffs[1] - diffs[0], diffs[-1] - diffs[-2], atol=1e-2): + ebins = 0.5 * (ebins[1:] + ebins[:-1]) + else: + ebins = (ebins[1:] * ebins[:-1]) ** 0.5 + + all_dims = {**times, "E": ebins} + all_coords = {**all_dims, "s": ("t", steps["s"])} + + def remap_name(name: str) -> str: + return name[1:] + + return xr.Dataset( + { + remap_name(spectrum): xr.DataArray( + da.stack( + [ + da.from_delayed( + self.__read_spectrum( + path=self.path, + reader=self.reader, + spectrum=spectrum, + step=step, + ), + shape=shape, + dtype="float", + ) + for step in valid_steps + ], + ), + name=remap_name(spectrum), + dims=all_dims, + coords=all_coords, + ) + for spectrum in spectra_names + }, + attrs=self.reader.ReadAttrsAtTimestep( + path=self.path, category="spectra", step=first_step + ), + ) + + def help_spectra(self, prepend="") -> str: + ret = f"{prepend}- use .sel(...) to select specific energy or time intervals\n" + ret += f"{prepend} t : time (float)\n" + ret += f"{prepend} st : step (int)\n" + ret += f"{prepend} E : energy bin (float)\n{prepend}\n" + ret += f"{prepend} # example:\n" + ret += f"{prepend} # .sel(E=slice(10.0, 20.0)).sel(t=0, method='nearest')\n{prepend}\n" + ret += f"{prepend}- use .isel(...) to select spectra based on energy bin or time index:\n" + ret += f"{prepend} t : timestamp index (int)\n" + ret += f"{prepend} st : step index (int)\n" + ret += f"{prepend} E : energy bin index (int)\n{prepend}\n" + ret += f"{prepend} # example:\n" + ret += f"{prepend} # .isel(t=-1, E=11)\n" + ret += f"{prepend}\n" + ret += f"{prepend} # example:\n" + ret += f"{prepend} # .spectra.N_1.sel(E=slice(None, 50)).isel(t=5).plot()\n" + return ret diff --git a/nt2/readers/adios2.py b/nt2/readers/adios2.py index ac352cf..e01d4a0 100644 --- a/nt2/readers/adios2.py +++ b/nt2/readers/adios2.py @@ -142,6 +142,24 @@ def ReadArrayShapeAtTimestep( f"{category.capitalize()} {quantity} not found in the {filename}" ) + @override + def ReadArrayShapeExplicitlyAtTimestep( + self, path: str, category: str, quantity: str, step: int + ) -> tuple[int]: + with bp.FileReader(filename := self.FullPath(path, category, step)) as f: + if quantity in f.available_variables(): + var = f.inquire_variable(quantity) + if var is not None and (read := f.read(var)) is not None: + return read.shape + else: + raise ValueError( + f"{category.capitalize()} {quantity} is not a group in the {filename}" + ) + else: + raise ValueError( + f"{category.capitalize()} {quantity} not found in the {filename}" + ) + @override def ReadFieldCoordsAtTimestep( self, path: str, step: int diff --git a/nt2/readers/base.py b/nt2/readers/base.py index 7f31551..1b21a06 100644 --- a/nt2/readers/base.py +++ b/nt2/readers/base.py @@ -226,6 +226,36 @@ def ReadArrayShapeAtTimestep( """ raise NotImplementedError("ReadArrayShapeAtTimestep is not implemented") + def ReadArrayShapeExplicitlyAtTimestep( + self, + path: str, + category: str, + quantity: str, + step: int, + ) -> tuple[int]: + """Read the shape of an array at a given timestep, without relying on metadata. + + Parameters + ---------- + path : str + The path to the files. + category : str + The category of the files. + quantity : str + The name of the quantity to be read. + step : int + The timestep to be read. + + Returns + ------- + tuple[int] + The shape of the array at a given timestep. + """ + + raise NotImplementedError( + "ReadArrayShapeExplicitlyAtTimestep is not implemented" + ) + def ReadFieldCoordsAtTimestep( self, path: str, diff --git a/nt2/readers/hdf5.py b/nt2/readers/hdf5.py index 435185f..4217753 100644 --- a/nt2/readers/hdf5.py +++ b/nt2/readers/hdf5.py @@ -1,4 +1,6 @@ -from typing import Any +from __future__ import annotations + +from typing import Any, TYPE_CHECKING import sys @@ -15,18 +17,34 @@ def override(method): import numpy as np import numpy.typing as npt -import h5py +try: + import h5py # runtime +except ImportError: # pragma: no cover + h5py = None # type: ignore[assignment] + +if TYPE_CHECKING: + import h5py as _h5py from nt2.utils import Format, Layout from nt2.readers.base import BaseReader +def _require_h5py(): + if h5py is None: + raise ImportError( + "HDF5 support requires the optional dependency 'h5py'. " + "Install it with: pip install nt2py[hdf5]" + ) + return h5py + + class Reader(BaseReader): @staticmethod - def __extract_step0(f: h5py.File) -> h5py.Group: + def __extract_step0(f: "_h5py.File") -> "_h5py.Group": + h5 = _require_h5py() if "Step0" in f.keys(): f0 = f["Step0"] - if isinstance(f0, h5py.Group): + if isinstance(f0, h5.Group): return f0 else: raise ValueError(f"Step0 is not a group in the HDF5 file") @@ -42,8 +60,9 @@ def format(self) -> Format: @override def EnterFile( filename: str, - ) -> h5py.File: - return h5py.File(filename, "r") + ) -> "_h5py.File": + h5 = _require_h5py() + return h5.File(filename, "r") @override def ReadPerTimestepVariable( @@ -54,15 +73,16 @@ def ReadPerTimestepVariable( newname: str, ) -> dict[str, npt.NDArray[Any]]: variables: list[Any] = [] + h5 = _require_h5py() for filename in self.GetValidFiles( path=path, category=category, ): - with h5py.File(os.path.join(path, category, filename), "r") as f: + with h5.File(os.path.join(path, category, filename), "r") as f: f0 = Reader.__extract_step0(f) if varname in f0.keys(): var = f0[varname] - if isinstance(var, h5py.Dataset): + if isinstance(var, h5.Dataset): variables.append(var[()]) else: raise ValueError( @@ -80,7 +100,8 @@ def ReadAttrsAtTimestep( category: str, step: int, ) -> dict[str, Any]: - with h5py.File(self.FullPath(path, category, step), "r") as f: + h5 = _require_h5py() + with h5.File(self.FullPath(path, category, step), "r") as f: return {k: v for k, v in f.attrs.items()} @override @@ -89,7 +110,8 @@ def ReadEdgeCoordsAtTimestep( path: str, step: int, ) -> dict[str, npt.NDArray[Any]]: - with h5py.File(self.FullPath(path, "fields", step), "r") as f: + h5 = _require_h5py() + with h5.File(self.FullPath(path, "fields", step), "r") as f: f0 = Reader.__extract_step0(f) return {k: v[:] for k, v in f0.items() if k[0] == "X" and k[-1] == "e"} @@ -101,11 +123,12 @@ def ReadArrayAtTimestep( quantity: str, step: int, ) -> npt.NDArray[Any]: - with h5py.File(filename := self.FullPath(path, category, step), "r") as f: + h5 = _require_h5py() + with h5.File(filename := self.FullPath(path, category, step), "r") as f: f0 = Reader.__extract_step0(f) if quantity in f0.keys(): var = f0[quantity] - if isinstance(var, h5py.Dataset): + if isinstance(var, h5.Dataset): return np.array(var[:]) else: raise ValueError(f"{quantity} is not a group in the {filename}") @@ -120,7 +143,8 @@ def ReadCategoryNamesAtTimestep( prefix: str, step: int, ) -> set[str]: - with h5py.File(self.FullPath(path, category, step), "r") as f: + h5 = _require_h5py() + with h5.File(self.FullPath(path, category, step), "r") as f: f0 = Reader.__extract_step0(f) keys: list[str] = list(f0.keys()) return set(c for c in keys if c.startswith(prefix)) @@ -129,11 +153,12 @@ def ReadCategoryNamesAtTimestep( def ReadArrayShapeAtTimestep( self, path: str, category: str, quantity: str, step: int ) -> tuple[int]: - with h5py.File(filename := self.FullPath(path, category, step), "r") as f: + h5 = _require_h5py() + with h5.File(filename := self.FullPath(path, category, step), "r") as f: f0 = Reader.__extract_step0(f) if quantity in f0.keys(): var = f0[quantity] - if isinstance(var, h5py.Dataset): + if isinstance(var, h5.Dataset): return var.shape else: raise ValueError( @@ -144,16 +169,37 @@ def ReadArrayShapeAtTimestep( f"{category.capitalize()} {quantity} not found in the {filename}" ) + @override + def ReadArrayShapeExplicitlyAtTimestep( + self, path: str, category: str, quantity: str, step: int + ) -> tuple[int]: + h5 = _require_h5py() + with h5.File(self.FullPath(path, category, step), "r") as f: + f0 = Reader.__extract_step0(f) + if quantity in f0.keys(): + var = f0[quantity] + if isinstance(var, h5.Dataset) and (read := var[:]) is not None: + return read.shape + else: + raise ValueError( + f"{category.capitalize()} {quantity} is not a group in the HDF5 file" + ) + else: + raise ValueError( + f"{category.capitalize()} {quantity} not found in the HDF5 file" + ) + @override def ReadFieldCoordsAtTimestep( self, path: str, step: int ) -> dict[str, npt.NDArray[Any]]: - with h5py.File(filename := self.FullPath(path, "fields", step), "r") as f: + h5 = _require_h5py() + with h5.File(filename := self.FullPath(path, "fields", step), "r") as f: f0 = Reader.__extract_step0(f) def get_coord(c: str) -> Any: f0_c = f0[c] - if isinstance(f0_c, h5py.Dataset): + if isinstance(f0_c, h5.Dataset): return f0_c[:] else: raise ValueError(f"Field {c} is not a group in the {filename}") @@ -163,7 +209,8 @@ def get_coord(c: str) -> Any: @override def ReadFieldLayoutAtTimestep(self, path: str, step: int) -> Layout: - with h5py.File(filename := self.FullPath(path, "fields", step), "r") as f: + h5 = _require_h5py() + with h5.File(filename := self.FullPath(path, "fields", step), "r") as f: if "LayoutRight" not in f.attrs: raise ValueError(f"LayoutRight attribute not found in the {filename}") return Layout.R if f.attrs["LayoutRight"] else Layout.L diff --git a/pyproject.toml b/pyproject.toml index 9d25aef..2ea2644 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ dependencies = [ "xarray", "numpy", "scipy", - "h5py", "matplotlib", "tqdm", "contourpy", @@ -41,6 +40,9 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] +[project.optional-dependencies] +hdf5 = ["h5py"] + [project.urls] Repository = "https://github.com/entity-toolkit/nt2py" diff --git a/shell.nix b/shell.nix index d7ee1a0..77eed82 100644 --- a/shell.nix +++ b/shell.nix @@ -1,6 +1,6 @@ { pkgs ? import { }, - py ? "312", + py ? "313", }: pkgs.mkShell {