|
| 1 | +#!/usr/bin/env python |
| 2 | +import decimal |
| 3 | +import os |
| 4 | +from argparse import ArgumentParser |
| 5 | + |
| 6 | +DEFAULT_PRECISION = 16 |
| 7 | +DEFAULT_COUNT = 9 |
| 8 | + |
| 9 | + |
| 10 | +def decimal_pi() -> decimal.Decimal: |
| 11 | + """Compute Pi to the current precision. |
| 12 | +
|
| 13 | + >>> print(pdecimal_pii()) |
| 14 | + 3.141592653589793238462643383 |
| 15 | +
|
| 16 | + """ |
| 17 | + decimal.getcontext().prec += 2 # extra digits for intermediate steps |
| 18 | + three: decimal.Decimal = decimal.Decimal(3) # substitute "three=3.0" for regular floats |
| 19 | + lasts: decimal.Decimal = decimal.Decimal(0) |
| 20 | + t: decimal.Decimal = three |
| 21 | + n, na, d, da = 1, 0, 0, 24 |
| 22 | + s: decimal.Decimal = three |
| 23 | + while s != lasts: |
| 24 | + lasts = s |
| 25 | + n, na = n + na, na + 8 |
| 26 | + d, da = d + da, da + 32 |
| 27 | + t = (t * n) / d |
| 28 | + s += decimal.Decimal(t) |
| 29 | + decimal.getcontext().prec -= 2 |
| 30 | + return +s # unary plus applies the new precision |
| 31 | + |
| 32 | + |
| 33 | +def decimal_sin(x: decimal.Decimal) -> decimal.Decimal: |
| 34 | + """Return the sine of x as measured in radians. |
| 35 | +
|
| 36 | + The Taylor series approximation works best for a small value of x. |
| 37 | + For larger values, first compute x = x % (2 * pi). |
| 38 | +
|
| 39 | + >>> print(decimal_sin(Decimal('0.5'))) |
| 40 | + 0.4794255386042030002732879352 |
| 41 | + >>> print(decimal_sin(0.5)) |
| 42 | + 0.479425538604 |
| 43 | + >>> print(decimal_sin(0.5+0j)) |
| 44 | + (0.479425538604+0j) |
| 45 | +
|
| 46 | + """ |
| 47 | + decimal.getcontext().prec += 2 |
| 48 | + i, fact, num, sign = 1, 1, x, 1 |
| 49 | + s: decimal.Decimal = x |
| 50 | + lasts: decimal.Decimal = decimal.Decimal(0) |
| 51 | + while s != lasts: |
| 52 | + lasts = s |
| 53 | + i += 2 |
| 54 | + fact *= i * (i - 1) |
| 55 | + num *= x * x |
| 56 | + sign *= -1 |
| 57 | + s += num / fact * sign |
| 58 | + decimal.getcontext().prec -= 2 |
| 59 | + return +s |
| 60 | + |
| 61 | + |
| 62 | +def generate_sin_lut(precision: int, count_log2: int): |
| 63 | + one = 1 << precision |
| 64 | + count = 1 << count_log2 |
| 65 | + |
| 66 | + SinLut = [] |
| 67 | + |
| 68 | + for i in range(count): |
| 69 | + angle: decimal.Decimal = 2 * decimal_pi() * i / count |
| 70 | + |
| 71 | + sin_value: decimal.Decimal = decimal_sin(angle) # sin(angle) |
| 72 | + moved_sin: decimal.Decimal = sin_value * one |
| 73 | + rounded_sin: int = ( |
| 74 | + int(moved_sin + decimal.Decimal(0.5)) if moved_sin > 0 else int(moved_sin - decimal.Decimal(0.5)) |
| 75 | + ) |
| 76 | + SinLut.append(rounded_sin) |
| 77 | + |
| 78 | + SinLut.append(SinLut[0]) |
| 79 | + |
| 80 | + generated_options = "" |
| 81 | + if DEFAULT_PRECISION is not precision or DEFAULT_COUNT is not count_log2: |
| 82 | + generated_options += " with `" |
| 83 | + |
| 84 | + if DEFAULT_PRECISION is not precision: |
| 85 | + generated_options += f"-p {precision}" |
| 86 | + if DEFAULT_COUNT is not count_log2: |
| 87 | + generated_options += " " |
| 88 | + |
| 89 | + if DEFAULT_COUNT is not count_log2: |
| 90 | + generated_options += f"-c {count_log2}" |
| 91 | + |
| 92 | + generated_options += "`" |
| 93 | + |
| 94 | + source = f"""// This file was generated using the `misc/scripts/sin_lut_generator.py` script{generated_options}. |
| 95 | +
|
| 96 | +#pragma once |
| 97 | +
|
| 98 | +#include <array> |
| 99 | +#include <cstdint> |
| 100 | +
|
| 101 | +static constexpr uint32_t SIN_LUT_PRECISION = {precision}; |
| 102 | +static constexpr uint32_t SIN_LUT_COUNT_LOG2 = {count_log2}; |
| 103 | +static constexpr int32_t SIN_LUT_SHIFT = SIN_LUT_PRECISION - SIN_LUT_COUNT_LOG2; |
| 104 | +
|
| 105 | +static constexpr std::array<int64_t, (1 << SIN_LUT_COUNT_LOG2) + 1> SIN_LUT = {{ |
| 106 | +""" |
| 107 | + |
| 108 | + VALS_PER_LINE = 16 |
| 109 | + |
| 110 | + lines = [SinLut[i : i + VALS_PER_LINE] for i in range(0, len(SinLut), VALS_PER_LINE)] |
| 111 | + |
| 112 | + for line in lines[:-1]: |
| 113 | + source += f"\t{', '.join(str(value) for value in line)},\n" |
| 114 | + |
| 115 | + source += f"\t{', '.join(str(value) for value in lines[-1])}\n" |
| 116 | + source += "};\n" |
| 117 | + |
| 118 | + fixed_point_sin_path: str = os.path.join( |
| 119 | + os.path.dirname(__file__), "../../src/openvic-simulation/types/fixed_point/FixedPointLUT_sin.hpp" |
| 120 | + ) |
| 121 | + with open(fixed_point_sin_path, "w", newline="\n") as file: |
| 122 | + file.write(source) |
| 123 | + |
| 124 | + print("`FixedPointLUT_sin.hpp` generated successfully.") |
| 125 | + |
| 126 | + |
| 127 | +if __name__ == "__main__": |
| 128 | + parser = ArgumentParser(prog="Fixed Point Sin LUT Generator", description="Fixed-Point Sin Look-Up Table generator") |
| 129 | + parser.add_argument( |
| 130 | + "-p", |
| 131 | + "--precision", |
| 132 | + type=int, |
| 133 | + default=DEFAULT_PRECISION, |
| 134 | + choices=range(1, 65), |
| 135 | + help="The number of bits after the point (fractional bits)", |
| 136 | + ) |
| 137 | + parser.add_argument( |
| 138 | + "-c", |
| 139 | + "--count", |
| 140 | + type=int, |
| 141 | + default=DEFAULT_COUNT, |
| 142 | + choices=range(1, 65), |
| 143 | + help="The base 2 log of the number of values in the look-up table (must be <= precision)", |
| 144 | + ) |
| 145 | + args = parser.parse_args() |
| 146 | + |
| 147 | + if args.precision < args.count: |
| 148 | + print("ERROR: invalid count ", args.count, " - can't be greater than precision (", args.precision, ")") |
| 149 | + exit(-1) |
| 150 | + else: |
| 151 | + generate_sin_lut(args.precision, args.count) |
| 152 | + exit(0) |
0 commit comments