Dice simulator
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.idea
|
.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])
|
max_x = len(data[0])
|
||||||
start = (max_y-1, max_x-1)
|
start = (max_y-1, max_x-1)
|
||||||
|
|
||||||
# Cache with size of n points
|
# Table with size of data
|
||||||
cache = [[None for _ in range(max_x)] for _ in range(max_y)]
|
table = [[None for _ in range(max_x)] for _ in range(max_y)]
|
||||||
|
|
||||||
def find_path(y, x):
|
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):
|
if not (0 <= y < max_y and 0 <= x < max_x):
|
||||||
return (), float('-inf')
|
return (), float('-inf')
|
||||||
|
|
||||||
if cache[y][x] is not None:
|
# Table look up
|
||||||
return cache[y][x]
|
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:
|
if y == 0 and x == 0:
|
||||||
res = (((y, x),), data[y][x])
|
res = (((y, x),), data[y][x])
|
||||||
|
# Calculate defined recursion on all other existing positions
|
||||||
else:
|
else:
|
||||||
west_path, west_val = find_path(y, x-1)
|
west_path, west_val = find_path(y, x-1)
|
||||||
north_path, north_val = find_path(y-1, x)
|
north_path, north_val = find_path(y-1, x)
|
||||||
|
|
||||||
|
# Choose most valuable path to the considered position
|
||||||
if west_val > north_val:
|
if west_val > north_val:
|
||||||
res = (west_path + ((y, x),), west_val + data[y][x])
|
res = (west_path + ((y, x),), west_val + data[y][x])
|
||||||
else:
|
else:
|
||||||
res = (north_path + ((y, x),), north_val + data[y][x])
|
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
|
return res
|
||||||
|
|
||||||
path, val = find_path(*start)
|
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