Rust binding for python. To calculate star ratings and performance points for all osu! gamemodes, and quickly parse Beatmap into python objects.
Project description
peace-performance-python
Fast, To calculate star ratings and performance points for all osu! gamemodes, And, quickly parse Beatmap into python objects.
PP Version: 21.07.27
peace-performance (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.
Install by pip
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 ]
PP Calculate
Minimal Examples
from peace_performance_python.prelude import *
# Beatmap can be cached and reused!
beatmap = Beatmap('path_to_osu_file')
result = Calculator(acc=98.8, miss=3).calculate(beatmap)
# Async support!
beatmap = await Beatmap.create_async_rs('path_to_osu_file')
Full Examples
# import all
from peace_performance_python.prelude import *
# or
# from peace_performance_python.objects import Beatmap, Calculator
from tests import async_run, join_beatmap, HITORIGOTO, UNFORGIVING
# 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)
# or
c.acc = 98.8
c.miss = 3
# or
c.setattr('acc', 98.8)
c.setattr('miss', 3)
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())
Running results
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
}
}
...
Beatmap parse
Examples
from peace_performance_python.prelude import *
from tests import join_beatmap, HITORIGOTO
# 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()
Running results
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)>
Building
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
Compile to .whl
to use pip installation
maturin 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
Run tests and benchmarks
Once built, you can run the tests and benchmakrs using pytest
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
Features
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 |
Vs Oppai-ng
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
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Simple Benchmarks
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
MIT
pure-peace
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Hashes for peace-performance-python-1.0.4.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | 98863340fb0230923550853a59de3dda49074a04912e869f2b374b6eea3d612a |
|
MD5 | 3b969574ce21732075690cfc7ea284f0 |
|
BLAKE2b-256 | f80539483c59a955912c9cd3b583c64def321e6f046e6e3428999e6096e3ec64 |
Hashes for peace_performance_python-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8e703a8611680b58e458492a5a8b21372736acc46d64b9a1b872f6c3d85f0b18 |
|
MD5 | 6748e7c56589d2760b88b5e405372f57 |
|
BLAKE2b-256 | 2d25583f6fb0bc0e2b2c631fa907164334fe23d3b70df22ca4a1ada40eaf52ee |
Hashes for peace_performance_python-1.0.4-cp39-cp39-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 05d36397a3395d79cb16c4ae7626dce755dbf669c58dcb800acacd5d9de1fe06 |
|
MD5 | e2fcf1a1a85a1a7e7e3c56633dc2ae69 |
|
BLAKE2b-256 | b54e9e0ca1218200a357edcb742d955204ac12fa9b132dcd58966341cf9011c1 |
Hashes for peace_performance_python-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 71dd5110dc8aac6b0a511079a91536c68d05b2f8dea0547f085399c926839926 |
|
MD5 | 8e5f478a7db3a7960649b9b684eca2fa |
|
BLAKE2b-256 | ec6474fa359420260a1f95930a9c6551bcb2d83b576b671f718d4666d34bf346 |
Hashes for peace_performance_python-1.0.4-cp39-cp39-macosx_10_15_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8fd8c2ba0821074075f1dac89fe9e4a864bb43168472e139d5208bf79274d765 |
|
MD5 | a55c1cae9bd91bf209a84738a12255ed |
|
BLAKE2b-256 | 81d731280c96742bbcd0aa584a8a69fd8c119a5d7414cfad637e6a4193f2ce3c |
Hashes for peace_performance_python-1.0.4-cp38-cp38-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8a3a48d8ac7ba79371da45f8913c5335bb459a608be84d2c444b51dc69896e42 |
|
MD5 | 70abc2689de916a0b984aba48d1ee755 |
|
BLAKE2b-256 | 734d19d6d72257ca539cad0ac113cb1ad6aa8378a9fbe49f7d5a889238fe8757 |
Hashes for peace_performance_python-1.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 03d0251a8df0331de606a9cb563a93ddfd63708dc70ed22276e96a9caa3e5ab5 |
|
MD5 | 2693f485a427371eafa085753c8885e8 |
|
BLAKE2b-256 | 64dcd0ab30355f91d97faf3ff6f20579f5da42549158eabb0a36b2712a962d41 |
Hashes for peace_performance_python-1.0.4-cp38-cp38-macosx_10_15_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4456f0ef0483dcc6b9631df883554aa89056eb49ff791086c000d1cb81d6e30e |
|
MD5 | 71ca6118d73d511669ecb5c1a33fc2c4 |
|
BLAKE2b-256 | b0dfc8885c3de53ff2a5e17c584f791e4afab2cbf8164e0b1f423741eabed6ef |
Hashes for peace_performance_python-1.0.4-cp37-cp37m-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1096684d3f5fb81f81b2161246619c6958733facfcec95c43243f6e62483917c |
|
MD5 | 3b35680f712158d2aa6b5345b0fa92b3 |
|
BLAKE2b-256 | 2b37f12ffde1705c914a6f7a35246ea7637890a8324e57a9bba40a51b9984dad |
Hashes for peace_performance_python-1.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | dda5729c8db962b01135035cb893135089775600b95b320f84225f71b22cee27 |
|
MD5 | c811603304559bbdc66c3a1e7a9785e0 |
|
BLAKE2b-256 | 5efaffd69b606cb6d3c814bc8c20e653285e403f7b2cff85c42487282a429d9f |
Hashes for peace_performance_python-1.0.4-cp37-cp37m-macosx_10_15_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9b0b945a4e63bd53c739dc13f9c93defd109ca45e732bd2674aff2de054ddb67 |
|
MD5 | ed9ab2f74316e67051956b7fd18855cd |
|
BLAKE2b-256 | 0cfedff273fe8c363c18700c5f4067834da79dc03b1789ca8e594527e35680de |
Hashes for peace_performance_python-1.0.4-cp36-cp36m-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 16b48565629c180baa231d4c72ed8d6c08df92c4738385e92b6c54c43cfe2155 |
|
MD5 | 6b5acf9b8752cd740a97f989267982b7 |
|
BLAKE2b-256 | 9d2c7a82368b07693f47e247a90cd4bf64de5d3d13f9815f07bfcd45851e8545 |
Hashes for peace_performance_python-1.0.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e331c04e4035d85bb1f890289448255f853fca1a223cdc5ad2fadcea1c891d60 |
|
MD5 | 0d632b10747b813e5071dd1c5b303999 |
|
BLAKE2b-256 | 73288065ab860843c6c6523505d9907d877a77d5b98fcb151e361707c657d124 |
Hashes for peace_performance_python-1.0.4-cp36-cp36m-macosx_10_15_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f93a08311a4b73f22d8311f72100b1b959cb5369534ae8fb18b04ec320539b3d |
|
MD5 | a0744c32606504ccaa31d7f5bed71a60 |
|
BLAKE2b-256 | ed0c67a21f7eae2aca7164f09d4028779c54e2ff8966ab96f0f8e67a9ac80294 |