Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
peace-performance-python
Advanced tools
Rust binding for python. To calculate star ratings and performance points for all osu! gamemodes, and quickly parse Beatmap into python objects.
Fast, To calculate star ratings and performance points for all osu! gamemodes, And, quickly parse Beatmap into python objects.
rosu-pp (Rust) binding for python based on PyO3.
Cross-platform support. Support synchronous and asynchronous(tokio and async_std).
Faster than oppai-ng, see the benchmark for details.
PyPI: https://pypi.org/project/peace-performance-python
pip install peace-performance-python
Reliability:
check_your_osu_songs.py
Testing on my local machine, about 70,000 maps can be calculated properly.
[ Walking (E:\osu\songs) Start... ]
[ 69146 ] .osu files founded...
[ Walking Done! ]
[ Start task ]
69146/69146; ok: 69142, err: 0; total time: 23825.90ms, avg: 0.34ms
[ All DONE ]
from peace_performance_python.prelude import *
from peace_performance_python.objects.utils import Mods
# 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 = await Beatmap.create_async_rs('path_to_osu_file')
# import all
from peace_performance_python.prelude import *
# or
# from peace_performance_python.objects import Beatmap, Calculator
from peace_performance_python.objects.utils import Mods
from tests import async_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 like
def calculate(beatmap: Beatmap, calculator: Calculator) -> CalcResult:
return calculator.calculate(beatmap)
def calculate_2(beatmap: Beatmap) -> CalcResult:
# --
c = Calculator()
c.set_acc(98.8)
c.set_miss(3)
c.set_mods(Mods.HARDROCK | Mods.HIDDEN)
# or
c.acc = 98.8
c.miss = 3
c.mods = Mods.HARDROCK | Mods.HIDDEN
# or
c.setattr('acc', 98.8)
c.setattr('miss', 3)
c.setattr('mods', Mods.HARDROCK | Mods.HIDDEN)
return calculate(beatmap, c)
def calculate_3(beatmap: Beatmap) -> CalcResult:
c = Calculator()
c.set_with_dict({'acc': 98.8, 'miss': 3})
return calculate(beatmap, c)
def calculate_4(beatmap: Beatmap) -> CalcResult:
return Calculator({'acc': 98.8, 'miss': 3}).calculate(beatmap)
def calculate_5(beatmap: Beatmap) -> CalcResult:
return Calculator(acc=98.8, miss=3).calculate(beatmap)
async def main() -> None:
path = join_beatmap(HITORIGOTO)
# Load beatmap
beatmap = 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 calculator
c.reset()
print('\n>>>>> reseted Calculator as dict:', c.attrs_dict)
# Calc again
result2 = c.calculate(beatmap)
print('\n>>>>> result2 as dict:', result2.attrs_dict)
# Load another .osu files
path2 = join_beatmap(UNFORGIVING)
beatmap.init(path2)
print(beatmap)
# Convert calculate
result3 = Calculator(mode=3).calculate(beatmap)
print(result3)
if __name__ == '__main__':
async_run(main())
TRACE peace_performance_python::methods::common > function=sync_read_file duration=73.3µs
TRACE peace_performance_python::methods::pp > function=sync_parse_beatmap duration=181.9µs
>>>>> Beatmap: <Beatmap object (
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
)>
>>>>> Calculator as dict: {
'mode': None,
'mods': None,
'n50': None,
'n100': None,
'n300': None,
'katu': None,
'acc': 98.80000305175781,
'passed_obj': None,
'combo': None,
'miss': 3
}
TRACE peace_performance_python::methods::pp > function=calc_with_any_pp duration=55.7µs
TRACE peace_performance_python::objects::calculator > function=calc duration=103.2µs
>>>>> result.pp: 152.19204711914062
>>>>> result as dict: {
'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
}
}
...
from peace_performance_python.prelude import *
from tests import join_beatmap, HITORIGOTO
# *No longer available by default (compile without `rust_logger` features enabled)*
# Initialize Rust logger (optional)
'''
set_log_level('trace')
init_logger()
'''
def main():
path = join_beatmap(HITORIGOTO)
# Load beatmap
b = 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].pos
pos_1 = b.hit_objects[1].pos
print('\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()
TRACE peace_performance_python::methods::common > function=sync_read_file duration=78.3µs
TRACE peace_performance_python::methods::pp > function=sync_parse_beatmap duration=193.4µs
>>>>> Beatmap: <Beatmap object (
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): [
<HitObject object (
start_time: 536, sound: 4, end_time: 536, kind: circle, pos: (44, 136))>,
<HitObject object (
start_time: 717, sound: 0, end_time: 717, kind: circle, pos: (315, 196))>,
<HitObject object (
start_time: 899, sound: 0, end_time: 899, kind: slider, pos: (152, 304))>]
>>>>> Beatmap.timing_points: [<TimingPoint object (time: 536, beat_len: 363.63635)>]
>>>>> Beatmap.difficulty_points (0-3): [
<DifficultyPoint object (time: 23808, speed_multiplier: 1)>,
<DifficultyPoint object (time: 35445, speed_multiplier: 0.8)>,
<DifficultyPoint object (time: 41263, speed_multiplier: 1)>]
>>>>> Beatmap.hit_objects[0].pos: <Pos2 object (x: 44, y: 136)>
>>>>> Beatmap.hit_objects[3].kind: <HitObjectKind object (
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: [
<Pos2 object (x: 315, y: 196)>,
<Pos2 object (x: 277, y: 176)>,
<Pos2 object (x: 248, y: 145)>]
>>>>> Beatmap object(0): <HitObject object (
start_time: 536, sound: 4, end_time: 536, kind: circle, pos: (44, 136))>
>>>>> Beatmap object(1): <HitObject object (
start_time: 717, sound: 0, end_time: 717, kind: circle, pos: (315, 196))>
>>>>> Beatmap object pos(0): <Pos2 object (x: 44, y: 136)>
>>>>> Beatmap object pos(1): <Pos2 object (x: 315, y: 196)>
>>>>> Beatmap object pos(0) length, squared: 142.9405517578125 20432.0
>>>>> Beatmap object pos(0 and 1) distance: 277.5625915527344
>>>>> Beatmap object pos(0 and 1) add: <Pos2 object (x: 359, y: 332)>
>>>>> Beatmap object pos(0 and 1) sub: <Pos2 object (x: -271, y: -60)>
This package is intended to be built using rust
, maturin
or setuptools_rust
.
1. Install Rust
posix
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
windows
https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe
2. Install python dev dependencies
pip install -r requirements-dev.txt
3. Build native python lib
maturin develop --release
OR
python setup.py develop
.whl
to use pip installationmaturin build --release
OR
python setup.py bdist_wheel
install .whl
# maturin build
pip install target/wheels/<name>.whl
# setup.py build
pip install dist/<name>.whl
Build native modules (.pyd
or .so
)
python setup.py develop
Then run
pytest
or bench and draw a image
pytest --benchmark-histogram
Complete bench
pytest --benchmark-disable-gc --benchmark-warmup=True --benchmark-histogram
Run examples
python examples.py
Flag | Description |
---|---|
default | Enable async_tokio, all modes and choose the all_included version for osu!standard. Set default_features = false to disable. |
score_v2_buff | Buff ScoreV2 (STD) - acc *= 1.25 |
ppysb_edition | Special changes for RELAX and AUTOPILOT |
relax_nerf | Nerf relax and autopilot pp. Relax: aim * 0.9, spd * 0.3, acc *0.8 ; Autopilot: aim * 0.3, spd * 0.9, acc * 0.8 |
taiko | Enable osu!taiko. |
fruits | Enable osu!ctb. |
mania | Enable osu!mania. |
osu | Enable osu!standard. Requires to also enable exactly one of the features no_leniency , no_sliders_no_leniency , or all_included . |
no_leniency | When 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_leniency | When calculating difficulty attributes in osu!standard, ignore stack leniency and sliders. Best performance but slightly less precision than no_leniency . |
all_included | When calculating difficulty attributes in osu!standard, consider both stack leniency and sliders. Best precision but significantly worse performance than no_leniency . |
async_tokio | Beatmap parsing will be async through tokio |
async_std | Beatmap parsing will be async through async-std |
peace-performance Python bindings vs C89 oppai-ng Python bindings.
Rust is Faster. The longer the map, the more obvious the advantages of rust.
peace-performance enables the no_sliders_no_leniency
feature to be consistent with oppai's algorithm (faster, but loses precision).
If you need maximum precision (osu-performance) rather than performance, use all_included
features.
This test was run on my subsystem and had performance issues with a minimum time greater than 1ms. (The minimum time in windows is 86us (padoru) and the next smallest is 192us (hitorigoto)
------------------------------------------------------------------------ benchmark 'bench-oppai-vs-rust': 12 tests -------------------------------------------------------------------------
Name (time in ms) Min Max Mean StdDev Median IQR Outliers OPS Rounds Iterations
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_rust_padoru 1.3805 (1.0) 1.7463 (1.0) 1.4447 (1.0) 0.0518 (1.0) 1.4301 (1.0) 0.0444 (1.01) 68;32 692.1759 (1.0) 358 1
test_rust_hitorigoto 1.6154 (1.17) 2.0133 (1.15) 1.7433 (1.21) 0.0587 (1.13) 1.7407 (1.22) 0.0439 (1.0) 129;82 573.6085 (0.83) 498 1
test_oppai_padoru 1.8734 (1.36) 2.3294 (1.33) 1.9799 (1.37) 0.0845 (1.63) 1.9659 (1.37) 0.0931 (2.12) 47;11 505.0744 (0.73) 204 1
test_oppai_hitorigoto 2.5925 (1.88) 3.1272 (1.79) 2.7537 (1.91) 0.0883 (1.70) 2.7357 (1.91) 0.0887 (2.02) 84;21 363.1464 (0.52) 346 1
test_rust_freedom_dive 3.0829 (2.23) 3.6729 (2.10) 3.1865 (2.21) 0.0687 (1.33) 3.1715 (2.22) 0.0685 (1.56) 70;18 313.8282 (0.45) 303 1
test_rust_sotarks 3.6848 (2.67) 4.0976 (2.35) 3.7886 (2.62) 0.0748 (1.45) 3.7676 (2.63) 0.0748 (1.70) 51;17 263.9489 (0.38) 240 1
test_rust_galaxy_burst 5.1418 (3.72) 5.7422 (3.29) 5.2629 (3.64) 0.0851 (1.64) 5.2349 (3.66) 0.0911 (2.08) 44;9 190.0092 (0.27) 184 1
test_oppai_freedom_dive 5.8532 (4.24) 6.3883 (3.66) 6.0279 (4.17) 0.1154 (2.23) 5.9971 (4.19) 0.1660 (3.78) 45;2 165.8948 (0.24) 161 1
test_oppai_sotarks 6.2822 (4.55) 7.0327 (4.03) 6.4706 (4.48) 0.1350 (2.61) 6.4400 (4.50) 0.1544 (3.52) 36;4 154.5453 (0.22) 145 1
test_oppai_galaxy_burst 8.2669 (5.99) 9.4358 (5.40) 8.5453 (5.91) 0.1838 (3.55) 8.5012 (5.94) 0.1732 (3.95) 25;8 117.0232 (0.17) 114 1
test_rust_unforgiving 10.9350 (7.92) 11.6170 (6.65) 11.1305 (7.70) 0.1289 (2.49) 11.1028 (7.76) 0.1701 (3.87) 26;2 89.8429 (0.13) 88 1
test_oppai_unforgiving 22.8311 (16.54) 23.7671 (13.61) 23.1481 (16.02) 0.2352 (4.54) 23.0818 (16.14) 0.2999 (6.83) 10;1 43.2001 (0.06) 43 1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Native vs Wrapped beatmap object, Sync vs Async, Py async vs Rust async
Note: 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 methods
pure-peace
FAQs
Rust binding for python. To calculate star ratings and performance points for all osu! gamemodes, and quickly parse Beatmap into python objects.
We found that peace-performance-python demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.