Package: peace-performance-python
peace-performance-pythonFast, To calculate star ratings and performance points for all osu! gamemodes,And, quickly parse Beatmap into python objects.!! Now withhttps://github.com/MaxOhn/rosu-pprosu-pp(Rust) binding for python based onPyO3.Cross-platform support.
Support synchronous and asynchronous(tokio and async_std).Faster thanoppai-ng, see thebenchmarkfor details.Install by pipPyPI:https://pypi.org/project/peace-performance-pythonpipinstallpeace-performance-pythonReliability:check_your_osu_songs.pyTesting on my local machine, about 70,000 maps can be calculated properly.[Walking(E:\osu\songs)Start...][69146].osufilesfounded...[WalkingDone!][Starttask]69146/69146;ok:69142,err:0;totaltime:23825.90ms,avg:0.34ms[AllDONE]PP CalculateMinimal Examplesfrompeace_performance_python.preludeimport*frompeace_performance_python.objects.utilsimportMods# Beatmap can be cached and reused!beatmap=Beatmap('path_to_osu_file')result=Calculator(acc=98.8,miss=3,mods=Mods.HARDROCK|Mods.HIDDEN).calculate(beatmap)# HDHR# Async support!beatmap=awaitBeatmap.create_async_rs('path_to_osu_file')Full Examples# import allfrompeace_performance_python.preludeimport*# or# from peace_performance_python.objects import Beatmap, Calculatorfrompeace_performance_python.objects.utilsimportModsfromtestsimportasync_run,join_beatmap,HITORIGOTO,UNFORGIVING# *No longer available by default (compile without `rust_logger` features enabled)*# Initialize Rust logger (optional)'''set_log_level('trace')init_logger()'''# Choose a style you likedefcalculate(beatmap:Beatmap,calculator:Calculator)->CalcResult:returncalculator.calculate(beatmap)defcalculate_2(beatmap:Beatmap)->CalcResult:# --c=Calculator()c.set_acc(98.8)c.set_miss(3)c.set_mods(Mods.HARDROCK|Mods.HIDDEN)# orc.acc=98.8c.miss=3c.mods=Mods.HARDROCK|Mods.HIDDEN# orc.setattr('acc',98.8)c.setattr('miss',3)c.setattr('mods',Mods.HARDROCK|Mods.HIDDEN)returncalculate(beatmap,c)defcalculate_3(beatmap:Beatmap)->CalcResult:c=Calculator()c.set_with_dict({'acc':98.8,'miss':3})returncalculate(beatmap,c)defcalculate_4(beatmap:Beatmap)->CalcResult:returnCalculator({'acc':98.8,'miss':3}).calculate(beatmap)defcalculate_5(beatmap:Beatmap)->CalcResult:returnCalculator(acc=98.8,miss=3).calculate(beatmap)asyncdefmain()->None:path=join_beatmap(HITORIGOTO)# Load beatmapbeatmap=Beatmap(path)# beatmap = Beatmap.create(path)# Async# beatmap = await Beatmap.create_async_rs(path)# beatmap = await Beatmap.create_async_py(path)# or# beatmap = await AsyncBeatmapRust(path)# beatmap = await AsyncBeatmapPython(path)print('\n>>>>> Beatmap:',beatmap)# Calculate pp# result = calculate_5(beatmap)c=Calculator(acc=98.8,miss=3)print('\n>>>>> Calculator as dict:',c.attrs_dict)result=c.calculate(beatmap)# Print results# print('\n>>>>> result:', result)print('\n>>>>> result.pp:',result.pp)print('\n>>>>> result as dict:',result.attrs_dict)# print('\n>>>>> result.raw_stars as dict:', result.raw_stars.attrs_dict)# print('\n>>>>> result.raw_pp as dict:', result.raw_pp.attrs_dict)# Reset calculatorc.reset()print('\n>>>>> reseted Calculator as dict:',c.attrs_dict)# Calc againresult2=c.calculate(beatmap)print('\n>>>>> result2 as dict:',result2.attrs_dict)# Load another .osu filespath2=join_beatmap(UNFORGIVING)beatmap.init(path2)print(beatmap)# Convert calculateresult3=Calculator(mode=3).calculate(beatmap)print(result3)if__name__=='__main__':async_run(main())Running resultsTRACEpeace_performance_python::methods::common>function=sync_read_fileduration=73.3µsTRACEpeace_performance_python::methods::pp>function=sync_parse_beatmapduration=181.9µs>>>>>Beatmap:<Beatmapobject(path:./test_beatmaps/hitorigoto.osu,is_initialized:True,mode:0,mode_str:std,version:14,n_circles:207,n_sliders:132,n_spinners:1,ar:9,od:8.5,cs:4,hp:6,sv:1.7,tick_rate:1,stack_leniency:None)>>>>>>Calculatorasdict:{'mode':None,'mods':None,'n50':None,'n100':None,'n300':None,'katu':None,'acc':98.80000305175781,'passed_obj':None,'combo':None,'miss':3}TRACEpeace_performance_python::methods::pp>function=calc_with_any_ppduration=55.7µsTRACEpeace_performance_python::objects::calculator>function=calcduration=103.2µs>>>>>result.pp:152.19204711914062>>>>>resultasdict:{'mode':0,'mods':0,'pp':152.19204711914062,'stars':5.162832260131836,'raw_pp':{'aim':73.0337905883789,'spd':31.048368453979492,'str':None,'acc':45.17241287231445,'total':152.19204711914062},'raw_stars':{'stars':5.162832260131836,'max_combo':476,'ar':9.0,'n_fruits':None,'n_droplets':None,'n_tiny_droplets':None,'od':8.5,'speed_strain':2.0723509788513184,'aim_strain':2.7511043548583984,'n_circles':207,'n_spinners':1}}...Beatmap parseExamplesfrompeace_performance_python.preludeimport*fromtestsimportjoin_beatmap,HITORIGOTO# *No longer available by default (compile without `rust_logger` features enabled)*# Initialize Rust logger (optional)'''set_log_level('trace')init_logger()'''defmain():path=join_beatmap(HITORIGOTO)# Load beatmapb=Beatmap(path)print('\n>>>>> Beatmap:',b)print('\n>>>>> Beatmap.hit_objects (0-3):',b.hit_objects[:3])print('\n>>>>> Beatmap.timing_points:',b.timing_points)print('\n>>>>> Beatmap.difficulty_points (0-3):',b.difficulty_points[:3])print('\n>>>>> Beatmap.hit_objects[0].pos:',b.hit_objects[0].pos)print('\n>>>>> Beatmap.hit_objects[3].kind:',b.hit_objects[3].kind)print('\n>>>>> Beatmap.hit_objects[3].kind.curve_points:',b.hit_objects[3].kind.curve_points)pos_0=b.hit_objects[0].pospos_1=b.hit_objects[1].posprint('\n>>>>> Beatmap object(0):',b.hit_objects[0])print('\n>>>>> Beatmap object(1):',b.hit_objects[1])print('\n>>>>> Beatmap object pos(0):',pos_0)print('\n>>>>> Beatmap object pos(1):',pos_1)print('\n>>>>> Beatmap object pos(0) length, squared:',pos_0.length,pos_0.length_squared)print('\n>>>>> Beatmap object pos(0 and 1) distance:',pos_0.distance(pos_1))print('\n>>>>> Beatmap object pos(0 and 1) add:',pos_0.add(pos_1))print('\n>>>>> Beatmap object pos(0 and 1) sub:',pos_0.sub(pos_1))if__name__=='__main__':main()Running resultsTRACEpeace_performance_python::methods::common>function=sync_read_fileduration=78.3µsTRACEpeace_performance_python::methods::pp>function=sync_parse_beatmapduration=193.4µs>>>>>Beatmap:<Beatmapobject(path:./test_beatmaps/hitorigoto.osu,is_initialized:True,mode:0,mode_str:std,version:14,n_circles:207,n_sliders:132,n_spinners:1,ar:9,od:8.5,cs:4,hp:6,sv:1.7,tick_rate:1,stack_leniency:None),hidden:hit_objects,timing_points,difficulty_points>>>>>>Beatmap.hit_objects(0-3):[<HitObjectobject(start_time:536,sound:4,end_time:536,kind:circle,pos:(44,136))>,<HitObjectobject(start_time:717,sound:0,end_time:717,kind:circle,pos:(315,196))>,<HitObjectobject(start_time:899,sound:0,end_time:899,kind:slider,pos:(152,304))>]>>>>>Beatmap.timing_points:[<TimingPointobject(time:536,beat_len:363.63635)>]>>>>>Beatmap.difficulty_points(0-3):[<DifficultyPointobject(time:23808,speed_multiplier:1)>,<DifficultyPointobject(time:35445,speed_multiplier:0.8)>,<DifficultyPointobject(time:41263,speed_multiplier:1)>]>>>>>Beatmap.hit_objects[0].pos:<Pos2object(x:44,y:136)>>>>>>Beatmap.hit_objects[3].kind:<HitObjectKindobject(kind:slider,pixel_len:Some(85.0),repeats:Some(1),path_type:Some("perfect_curve"),end_time:None)>>>>>>Beatmap.hit_objects[3].kind.curve_points:[<Pos2object(x:315,y:196)>,<Pos2object(x:277,y:176)>,<Pos2object(x:248,y:145)>]>>>>>Beatmapobject(0):<HitObjectobject(start_time:536,sound:4,end_time:536,kind:circle,pos:(44,136))>>>>>>Beatmapobject(1):<HitObjectobject(start_time:717,sound:0,end_time:717,kind:circle,pos:(315,196))>>>>>>Beatmapobjectpos(0):<Pos2object(x:44,y:136)>>>>>>Beatmapobjectpos(1):<Pos2object(x:315,y:196)>>>>>>Beatmapobjectpos(0)length,squared:142.940551757812520432.0>>>>>Beatmapobjectpos(0and1)distance:277.5625915527344>>>>>Beatmapobjectpos(0and1)add:<Pos2object(x:359,y:332)>>>>>>Beatmapobjectpos(0and1)sub:<Pos2object(x:-271,y:-60)>BuildingThis package is intended to be built usingrust,maturinorsetuptools_rust.1. Install Rustposixcurl--proto'=https'--tlsv1.2-sSfhttps://sh.rustup.rs|shwindowshttps://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe2. Install python dev dependenciespipinstall-rrequirements-dev.txt3. Build native python libmaturindevelop--releaseORpythonsetup.pydevelopCompile to.whlto use pip installationmaturinbuild--releaseORpythonsetup.pybdist_wheelinstall .whl# maturin buildpipinstalltarget/wheels/<name>.whl# setup.py buildpipinstalldist/<name>.whlRun tests and benchmarksBuild native modules (.pydor.so)pythonsetup.pydevelopThen runpytestor bench and draw a imagepytest--benchmark-histogramComplete benchpytest--benchmark-disable-gc--benchmark-warmup=True--benchmark-histogramRun examplespythonexamples.pyFeaturesFlagDescriptiondefaultEnableasync_tokio,all modesand choose theall_includedversion for osu!standard. Setdefault_features = falseto disable.score_v2_buffBuff ScoreV2 (STD)-acc *= 1.25ppysb_editionSpecial changes forRELAXandAUTOPILOTrelax_nerfNerf relax and autopilot pp.Relax:aim * 0.9, spd * 0.3, acc *0.8; Autopilot:aim * 0.3, spd * 0.9, acc * 0.8taikoEnable osu!taiko.fruitsEnable osu!ctb.maniaEnable osu!mania.osuEnable osu!standard. Requires to also enable exactly one of the featuresno_leniency,no_sliders_no_leniency, orall_included.no_leniencyWhen calculating difficulty attributes in osu!standard, ignore stack leniency but consider sliders. Solid middleground between performance and precision, hence the default version.no_sliders_no_leniencyWhen calculating difficulty attributes in osu!standard, ignore stack leniency and sliders. Best performance but slightly less precision thanno_leniency.all_includedWhen calculating difficulty attributes in osu!standard, consider both stack leniency and sliders. Best precision but significantly worse performance thanno_leniency.async_tokioBeatmap parsing will be async throughtokioasync_stdBeatmap parsing will be async throughasync-stdVs Oppai-ngpeace-performance Python bindings vsC89 oppai-ngPython bindings.Rust is Faster. The longer the map, the more obvious the advantages of rust.peace-performance enables theno_sliders_no_leniencyfeature to be consistent with oppai's algorithm (faster, but loses precision).If you need maximum precision (osu-performance) rather than performance, useall_includedfeatures.This test was run on my subsystem and had performance issues with a minimum time greater than 1ms.(The minimum time in windows is86us(padoru) and the next smallest is192us(hitorigoto)------------------------------------------------------------------------benchmark'bench-oppai-vs-rust':12tests-------------------------------------------------------------------------
Name(timeinms)MinMaxMeanStdDevMedianIQROutliersOPSRoundsIterations
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_rust_padoru1.3805(1.0)1.7463(1.0)1.4447(1.0)0.0518(1.0)1.4301(1.0)0.0444(1.01)68;32692.1759(1.0)3581test_rust_hitorigoto1.6154(1.17)2.0133(1.15)1.7433(1.21)0.0587(1.13)1.7407(1.22)0.0439(1.0)129;82573.6085(0.83)4981test_oppai_padoru1.8734(1.36)2.3294(1.33)1.9799(1.37)0.0845(1.63)1.9659(1.37)0.0931(2.12)47;11505.0744(0.73)2041test_oppai_hitorigoto2.5925(1.88)3.1272(1.79)2.7537(1.91)0.0883(1.70)2.7357(1.91)0.0887(2.02)84;21363.1464(0.52)3461test_rust_freedom_dive3.0829(2.23)3.6729(2.10)3.1865(2.21)0.0687(1.33)3.1715(2.22)0.0685(1.56)70;18313.8282(0.45)3031test_rust_sotarks3.6848(2.67)4.0976(2.35)3.7886(2.62)0.0748(1.45)3.7676(2.63)0.0748(1.70)51;17263.9489(0.38)2401test_rust_galaxy_burst5.1418(3.72)5.7422(3.29)5.2629(3.64)0.0851(1.64)5.2349(3.66)0.0911(2.08)44;9190.0092(0.27)1841test_oppai_freedom_dive5.8532(4.24)6.3883(3.66)6.0279(4.17)0.1154(2.23)5.9971(4.19)0.1660(3.78)45;2165.8948(0.24)1611test_oppai_sotarks6.2822(4.55)7.0327(4.03)6.4706(4.48)0.1350(2.61)6.4400(4.50)0.1544(3.52)36;4154.5453(0.22)1451test_oppai_galaxy_burst8.2669(5.99)9.4358(5.40)8.5453(5.91)0.1838(3.55)8.5012(5.94)0.1732(3.95)25;8117.0232(0.17)1141test_rust_unforgiving10.9350(7.92)11.6170(6.65)11.1305(7.70)0.1289(2.49)11.1028(7.76)0.1701(3.87)26;289.8429(0.13)881test_oppai_unforgiving22.8311(16.54)23.7671(13.61)23.1481(16.02)0.2352(4.54)23.0818(16.14)0.2999(6.83)10;143.2001(0.06)431--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Simple BenchmarksNativevsWrappedbeatmap object,SyncvsAsync,Py asyncvsRust asyncNote: Rust has its own event loop (independent of python) and has performance issues due to rust's need to convert built-in futures to python coroutine. So rust async is the worst performer.Read and parsing time spent on beatmap of different sizes(forgiving is a beatmap over 50 minutes long and takes the longest)There are also subtle differences in the different calling methodsMITpure-peace