Dice simulator
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
.idea
|
||||
.venv
|
||||
.venv
|
||||
*.build
|
||||
14
Uebung-01/decomposition.py
Normal file
14
Uebung-01/decomposition.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import math
|
||||
|
||||
def decomp(x: int, base: list[int]) -> dict[int, int]:
|
||||
base = sorted(base, reverse=True)
|
||||
|
||||
coef = {}
|
||||
for b in base:
|
||||
c = math.floor(x / b)
|
||||
coef[b] = c
|
||||
x -= b * c
|
||||
|
||||
return coef
|
||||
|
||||
print(decomp(25, [1, 5, 10, 25]))
|
||||
@@ -10,28 +10,34 @@ max_y = len(data)
|
||||
max_x = len(data[0])
|
||||
start = (max_y-1, max_x-1)
|
||||
|
||||
# Cache with size of n points
|
||||
cache = [[None for _ in range(max_x)] for _ in range(max_y)]
|
||||
# Table with size of data
|
||||
table = [[None for _ in range(max_x)] for _ in range(max_y)]
|
||||
|
||||
def find_path(y, x):
|
||||
# Return bad value for positions which are out of bounds
|
||||
if not (0 <= y < max_y and 0 <= x < max_x):
|
||||
return (), float('-inf')
|
||||
|
||||
if cache[y][x] is not None:
|
||||
return cache[y][x]
|
||||
# Table look up
|
||||
if table[y][x] is not None:
|
||||
return table[y][x]
|
||||
|
||||
# End of the recursion is the starting position
|
||||
if y == 0 and x == 0:
|
||||
res = (((y, x),), data[y][x])
|
||||
# Calculate defined recursion on all other existing positions
|
||||
else:
|
||||
west_path, west_val = find_path(y, x-1)
|
||||
north_path, north_val = find_path(y-1, x)
|
||||
|
||||
# Choose most valuable path to the considered position
|
||||
if west_val > north_val:
|
||||
res = (west_path + ((y, x),), west_val + data[y][x])
|
||||
else:
|
||||
res = (north_path + ((y, x),), north_val + data[y][x])
|
||||
|
||||
cache[y][x] = res
|
||||
# Store most valuable path in the table and return
|
||||
table[y][x] = res
|
||||
return res
|
||||
|
||||
path, val = find_path(*start)
|
||||
|
||||
60
Uebung-02/dice.cpp
Normal file
60
Uebung-02/dice.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
#include <random>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Dice {
|
||||
std::vector<std::string> sides;
|
||||
std::vector<double> probs;
|
||||
std::mt19937 gen;
|
||||
std::discrete_distribution<> dist;
|
||||
|
||||
public:
|
||||
size_t accepted = 0;
|
||||
size_t rejected = 0;
|
||||
|
||||
Dice(const std::vector<std::string>& sides, const std::vector<double>& probs)
|
||||
: sides(sides), probs(probs), gen(std::random_device{}()), dist(probs.begin(), probs.end()) {}
|
||||
|
||||
std::string fair_dice() {
|
||||
while (true) {
|
||||
std::vector<std::string> res;
|
||||
for (size_t i = 0; i < sides.size(); ++i)
|
||||
res.push_back(sides[dist(gen)]);
|
||||
std::set<std::string> uniq(res.begin(), res.end());
|
||||
if (uniq.size() == res.size()) {
|
||||
++accepted;
|
||||
return res[0];
|
||||
}
|
||||
++rejected;
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string,int> throw_dice(int n) {
|
||||
std::map<std::string,int> res;
|
||||
for (auto& s : sides) res[s] = 0;
|
||||
for (int i = 0; i < n; ++i)
|
||||
res[fair_dice()]++;
|
||||
return res;
|
||||
}
|
||||
|
||||
std::pair<size_t,size_t> stats() const { return {accepted, rejected}; }
|
||||
|
||||
void reset_stats() { accepted = rejected = 0; }
|
||||
};
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
PYBIND11_MODULE(dice, m) {
|
||||
py::class_<Dice>(m, "Dice")
|
||||
.def(py::init<const std::vector<std::string>&, const std::vector<double>&>())
|
||||
.def("fair_dice", &Dice::fair_dice)
|
||||
.def("throw_dice", &Dice::throw_dice)
|
||||
.def("stats", &Dice::stats)
|
||||
.def("reset_stats", &Dice::reset_stats)
|
||||
.def_readwrite("accepted", &Dice::accepted)
|
||||
.def_readwrite("rejected", &Dice::rejected);
|
||||
}
|
||||
36
Uebung-02/dice.py
Normal file
36
Uebung-02/dice.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from lib.Natives import NativeLoader
|
||||
|
||||
def throw_dice_cpp(n: int, sides: list, probs: list):
|
||||
loader = NativeLoader()
|
||||
dice_mod = loader.cpp("dice")
|
||||
Dice = dice_mod.Dice
|
||||
d = Dice(sides, probs)
|
||||
d.reset_stats()
|
||||
res = d.throw_dice(n)
|
||||
accepted, rejected = d.stats()
|
||||
return res, accepted, rejected
|
||||
|
||||
|
||||
dice_sides = ['1', '2', '3', '4', '5', '6']
|
||||
dice_props = [0.1, 0.1, 0.1, 0.1, 0.1, 0.5]
|
||||
|
||||
coin_sides = ['Head', 'Number']
|
||||
coin_head = [0.1, 0.5]
|
||||
|
||||
rep = 100000
|
||||
|
||||
# --- Dice test ---
|
||||
res_cpp, acc_dice, rej_dice = throw_dice_cpp(rep, dice_sides, dice_props)
|
||||
total = sum(res_cpp.values())
|
||||
probs_estimated = {k: v / total for k, v in res_cpp.items()}
|
||||
print("C++ Dice result (relative):", probs_estimated)
|
||||
print(f"Accepted: {acc_dice/(acc_dice+rej_dice)*100:.2f}%, "
|
||||
f"Rejected: {rej_dice/(acc_dice+rej_dice)*100:.2f}%\n")
|
||||
|
||||
# --- Coin test ---
|
||||
res_coin, acc_coin, rej_coin = throw_dice_cpp(rep, coin_sides, coin_head)
|
||||
total_coin = sum(res_coin.values())
|
||||
probs_coin = {k: v / total_coin for k, v in res_coin.items()}
|
||||
print("C++ Coin result (relative):", probs_coin)
|
||||
print(f"Accepted: {acc_coin/(acc_coin+rej_coin)*100:.2f}%, "
|
||||
f"Rejected: {rej_coin/(acc_coin+rej_coin)*100:.2f}%")
|
||||
27
Uebung-02/minsearch.py
Normal file
27
Uebung-02/minsearch.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import random
|
||||
|
||||
numsTest = [1, 2, 3, 4, 5]
|
||||
|
||||
def search_min(nums):
|
||||
if len(nums) == 1:
|
||||
return nums[0]
|
||||
|
||||
num = nums[0]
|
||||
nums.remove(num)
|
||||
|
||||
# n-1 recursions
|
||||
rec_num = search_min(nums)
|
||||
|
||||
if rec_num < num:
|
||||
return rec_num
|
||||
else:
|
||||
# n-1 times called in worst case
|
||||
smaller = True
|
||||
for n in nums:
|
||||
if n <= num:
|
||||
smaller = False
|
||||
print('Hit')
|
||||
assert smaller == True
|
||||
return num
|
||||
|
||||
print(search_min(numsTest))
|
||||
159
lib/Natives.py
Normal file
159
lib/Natives.py
Normal file
@@ -0,0 +1,159 @@
|
||||
import ctypes
|
||||
import pathlib
|
||||
import subprocess
|
||||
import hashlib
|
||||
import json
|
||||
import platform
|
||||
import sys
|
||||
import importlib.util
|
||||
import sysconfig
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from typing import Callable, Any, Tuple
|
||||
|
||||
class LazyCall:
|
||||
def __init__(self, func: Callable[..., Any], args: Tuple[Any, ...]) -> None:
|
||||
self.func = func
|
||||
self.args = args
|
||||
|
||||
# Calls C function lazily and returns string
|
||||
def to_str(self) -> str:
|
||||
self.func.restype = ctypes.c_char_p
|
||||
res = self.func(*self.__convert_args())
|
||||
return res.decode() if res else ""
|
||||
|
||||
# Calls C function lazily and returns int
|
||||
def to_int(self) -> int:
|
||||
self.func.restype = ctypes.c_int
|
||||
return self.func(*self.__convert_args())
|
||||
|
||||
# Calls C function lazily and returns float
|
||||
def to_float(self) -> float:
|
||||
self.func.restype = ctypes.c_double
|
||||
return self.func(*self.__convert_args())
|
||||
|
||||
# Convert arguments to C types
|
||||
def __convert_args(self):
|
||||
out = []
|
||||
for a in self.args:
|
||||
if isinstance(a, str):
|
||||
out.append(ctypes.c_char_p(a.encode()))
|
||||
elif isinstance(a, int):
|
||||
out.append(ctypes.c_int(a))
|
||||
elif isinstance(a, float):
|
||||
out.append(ctypes.c_double(a))
|
||||
else:
|
||||
out.append(a)
|
||||
return out
|
||||
|
||||
|
||||
class FunctionWrapper:
|
||||
def __init__(self, lib: ctypes.CDLL, name: str) -> None:
|
||||
self._lib = lib
|
||||
self._name = name
|
||||
|
||||
# Prepares lazy function calls
|
||||
def __call__(self, *args) -> LazyCall:
|
||||
func = getattr(self._lib, self._name)
|
||||
return LazyCall(func, args)
|
||||
|
||||
|
||||
class LibWrapper:
|
||||
def __init__(self, lib: ctypes.CDLL) -> None:
|
||||
self._lib = lib
|
||||
|
||||
# Exposes all C functions within the lib
|
||||
def __getattr__(self, name: str) -> FunctionWrapper:
|
||||
return FunctionWrapper(self._lib, name)
|
||||
|
||||
|
||||
class NativeLoader:
|
||||
def __init__(self) -> None:
|
||||
self.build_dir = pathlib.Path(".build")
|
||||
self.build_dir.mkdir(exist_ok=True)
|
||||
self.ext_suffix = sysconfig.get_config_var("EXT_SUFFIX") or sysconfig.get_config_var("SO")
|
||||
|
||||
# Loads a Cpp class
|
||||
def cpp(self, path: str):
|
||||
src = pathlib.Path(path).with_suffix(".cpp")
|
||||
if not src.exists():
|
||||
raise FileNotFoundError(src)
|
||||
out = self.build_dir / f"{src.stem}_cpp"
|
||||
|
||||
if self.__needs_rebuild(src, out):
|
||||
target = self.__build_cpp(src, out)
|
||||
else:
|
||||
target = str(out) + ".so"
|
||||
|
||||
spec = importlib.util.spec_from_file_location(src.stem, target)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
sys.modules[src.stem] = mod
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
# Loads a C lib
|
||||
def c(self, path: str) -> LibWrapper:
|
||||
src = pathlib.Path(path).with_suffix(".c")
|
||||
if not src.exists():
|
||||
raise FileNotFoundError(src)
|
||||
out = self.build_dir / f"{src.stem}_c.so"
|
||||
if self.__needs_rebuild(src, out):
|
||||
self.__build_c(src, out)
|
||||
lib = ctypes.CDLL(str(out))
|
||||
return LibWrapper(lib)
|
||||
|
||||
@staticmethod
|
||||
def __create_hash(path: pathlib.Path) -> str:
|
||||
h = hashlib.sha256()
|
||||
with open(path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(8192), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def __info_path(target: pathlib.Path) -> pathlib.Path:
|
||||
return target.with_suffix(target.suffix + ".buildinfo")
|
||||
|
||||
@staticmethod
|
||||
def __load_info(info_path: pathlib.Path):
|
||||
if info_path.exists():
|
||||
with open(info_path, "r") as f:
|
||||
return json.load(f)
|
||||
return None
|
||||
|
||||
def __write_info(self, info_path: pathlib.Path, src: pathlib.Path, out: pathlib.Path) -> None:
|
||||
data = {
|
||||
"os": platform.system(),
|
||||
"build_date": datetime.now().isoformat(),
|
||||
"src": str(src),
|
||||
"out": str(out),
|
||||
"src_hash": self.__create_hash(src),
|
||||
}
|
||||
with open(info_path, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
def __needs_rebuild(self, src: pathlib.Path, out: pathlib.Path) -> bool:
|
||||
info = self.__load_info(self.__info_path(out))
|
||||
final_out = pathlib.Path(str(out) + ".so")
|
||||
if not info or not final_out.exists():
|
||||
return True
|
||||
try:
|
||||
return info.get("src_hash") != self.__create_hash(src)
|
||||
except FileNotFoundError:
|
||||
return True
|
||||
|
||||
def __build_cpp(self, src: pathlib.Path, out: pathlib.Path) -> str:
|
||||
includes = (subprocess.check_output(["python3", "-m", "pybind11", "--includes"]).decode().strip().split())
|
||||
|
||||
built_temp = str(out) + self.ext_suffix
|
||||
final_path = str(out) + ".so"
|
||||
|
||||
subprocess.run(["c++", "-O3", "-shared", "-fPIC", "-std=c++17", *includes, str(src), "-o", built_temp], check=True)
|
||||
|
||||
shutil.move(built_temp, final_path)
|
||||
self.__write_info(self.__info_path(out), src, out)
|
||||
return final_path
|
||||
|
||||
def __build_c(self, src: pathlib.Path, out: pathlib.Path) -> None:
|
||||
subprocess.run(["gcc", "-O3", "-shared", "-fPIC", "-std=c11", str(src), "-o", str(out)], check=True)
|
||||
self.__write_info(self.__info_path(out), src, out)
|
||||
BIN
lib/__pycache__/Natives.cpython-313.pyc
Normal file
BIN
lib/__pycache__/Natives.cpython-313.pyc
Normal file
Binary file not shown.
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
pybind11>=2.12
|
||||
setuptools>=70.0
|
||||
wheel>=0.44
|
||||
Reference in New Issue
Block a user