Skip to content

Commit b9292f9

Browse files
committed
Add custom atan2, sin, and cos to experimental module
1 parent 461c213 commit b9292f9

1 file changed

Lines changed: 39 additions & 14 deletions

File tree

pybricks/experimental/pb_module_experimental.c

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,22 @@ static const float INV_TWO_PI_F = 0.1591549431f;
3030
// Internal Math Engines
3131
// -----------------------------------------------------------------------------
3232

33-
// High-Precision Sine (5th-degree Minimax Polynomial)
34-
// This is the core engine for both sin and cos.
3533
static float fast_sin_internal(float theta) {
36-
// Range reduction to [-PI, PI]
3734
float quot = theta * INV_TWO_PI_F;
3835
float x = theta - (float)((int)(quot + (quot > 0 ? 0.5f : -0.5f))) * TWO_PI_F;
3936

40-
// Symmetry reduction to [-PI/2, PI/2]
4137
if (x > HALF_PI_F) x = PI_F - x;
4238
else if (x < -HALF_PI_F) x = -PI_F - x;
4339

44-
// 5th-degree Minimax Polynomial
4540
float x2 = x * x;
4641
return x * (1.0f + x2 * (-0.1666665f + x2 * 0.0083322f));
4742
}
4843

49-
// Atan2 using the 0.273 parabolic approximation (valid for |z| <= 1)
5044
static float fast_atan2_internal(float y, float x) {
5145
float ay = fabsf(y) + 1e-10f;
5246
float ax = fabsf(x);
5347
float z, angle;
5448

55-
// Range reduction to ensure we stay on the stable part of the approximation
5649
if (ax >= ay) {
5750
z = y / ax;
5851
angle = (0.7853982f + 0.273f * (1.0f - fabsf(z))) * z;
@@ -71,21 +64,18 @@ static float fast_atan2_internal(float y, float x) {
7164
// MicroPython Functions
7265
// -----------------------------------------------------------------------------
7366

74-
// pybricks.experimental.sin(radians)
7567
static mp_obj_t experimental_sin(mp_obj_t theta_in) {
7668
float theta = mp_obj_get_float(theta_in);
7769
return mp_obj_new_float_from_f(fast_sin_internal(theta));
7870
}
7971
static MP_DEFINE_CONST_FUN_OBJ_1(experimental_sin_obj, experimental_sin);
8072

81-
// pybricks.experimental.cos(radians)
8273
static mp_obj_t experimental_cos(mp_obj_t theta_in) {
8374
float theta = mp_obj_get_float(theta_in);
8475
return mp_obj_new_float_from_f(fast_sin_internal(theta + HALF_PI_F));
8576
}
8677
static MP_DEFINE_CONST_FUN_OBJ_1(experimental_cos_obj, experimental_cos);
8778

88-
// pybricks.experimental.atan2(y, x)
8979
static mp_obj_t experimental_atan2(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
9080
PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args,
9181
PB_ARG_REQUIRED(y),
@@ -98,15 +88,50 @@ static mp_obj_t experimental_atan2(size_t n_args, const mp_obj_t *pos_args, mp_m
9888
}
9989
static MP_DEFINE_CONST_FUN_OBJ_KW(experimental_atan2_obj, 2, experimental_atan2);
10090

91+
// -----------------------------------------------------------------------------
92+
// New: Detailed Internal Benchmark
93+
// -----------------------------------------------------------------------------
94+
95+
// Runs sin, cos, and atan2 inside a C loop to measure pure CPU performance
96+
// Returns a tuple: (total_time_ms, avg_ns_per_triple_op)
97+
static mp_obj_t experimental_benchmark_internal(mp_obj_t n_in) {
98+
int32_t n = mp_obj_get_int(n_in);
99+
volatile float result = 0.0f;
100+
101+
uint32_t start = mp_hal_ticks_ms();
102+
103+
for (int32_t i = 0; i < n; i++) {
104+
// We use the internal engines directly to bypass ANY MicroPython overhead
105+
result += fast_sin_internal(1.23f);
106+
result += fast_sin_internal(1.23f + HALF_PI_F); // Cos
107+
result += fast_atan2_internal(1.23f, 1.23f);
108+
}
109+
110+
uint32_t end = mp_hal_ticks_ms();
111+
uint32_t total_ms = end - start;
112+
113+
// Calculate nanoseconds per loop (1ms = 1,000,000ns)
114+
// Avoid division by zero
115+
float ns_per_op = (n > 0) ? ((float)total_ms * 1000000.0f) / n : 0;
116+
117+
mp_obj_t tuple[2];
118+
tuple[0] = mp_obj_new_int(total_ms);
119+
tuple[1] = mp_obj_new_float(ns_per_op);
120+
121+
return mp_obj_new_tuple(2, tuple);
122+
}
123+
static MP_DEFINE_CONST_FUN_OBJ_1(experimental_benchmark_internal_obj, experimental_benchmark_internal);
124+
101125
// -----------------------------------------------------------------------------
102126
// Module Registry
103127
// -----------------------------------------------------------------------------
104128

105129
static const mp_rom_map_elem_t experimental_globals_table[] = {
106-
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_experimental) },
107-
{ MP_ROM_QSTR(MP_QSTR_sin), MP_ROM_PTR(&experimental_sin_obj) },
108-
{ MP_ROM_QSTR(MP_QSTR_cos), MP_ROM_PTR(&experimental_cos_obj) },
109-
{ MP_ROM_QSTR(MP_QSTR_atan2), MP_ROM_PTR(&experimental_atan2_obj) },
130+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_experimental) },
131+
{ MP_ROM_QSTR(MP_QSTR_sin), MP_ROM_PTR(&experimental_sin_obj) },
132+
{ MP_ROM_QSTR(MP_QSTR_cos), MP_ROM_PTR(&experimental_cos_obj) },
133+
{ MP_ROM_QSTR(MP_QSTR_atan2), MP_ROM_PTR(&experimental_atan2_obj) },
134+
{ MP_ROM_QSTR(MP_QSTR_benchmark_internal), MP_ROM_PTR(&experimental_benchmark_internal_obj) },
110135
};
111136
static MP_DEFINE_CONST_DICT(pb_module_experimental_globals, experimental_globals_table);
112137

0 commit comments

Comments
 (0)