Compare commits

...

13 Commits

20 changed files with 235 additions and 234 deletions

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
data
*/models*
venv
.venv
*/out*
python/__pycache__
cpp/bin

View File

@ -1,6 +1,6 @@
# Viola Jones
*Lisez ceci dans d'autres langues: [English](README.md)*
*Lisez ceci dans d'autres langues : [English](README.md)*
## Description
@ -23,7 +23,7 @@ Implémentation de l'algorithme "Viola Jones" en Python et C++.
Vous pouvez configurer l'algorithme avec les variables globales définies au début du fichier *ViolaJones.cpp* puis lancer 'make start'.
Il y a également la commande 'make clean' qui permet de supprimer tout fichiers compilées.
Il y a également la commande 'make clean' qui permet de supprimer tous fichiers compilés.
### Python
@ -34,7 +34,7 @@ Vous pouvez configurer l'algorithme dans le fichier *config.py* puis lancer l'al
## Entraînement
L'algorithme à été entraîné avec un processeur Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz et un GPU NVIDIA GeForce RTX 2080 Ti.
L'algorithme a été entraîné avec un processeur Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz et un GPU NVIDIA GeForce RTX 2080 Ti.
### Tableau de comparaison des temps d'exécution
@ -66,7 +66,7 @@ Il se trouve que le GPU bat systématiquement le CPU en matière de temps d'exé
L'algorithme de ViolaJones étant déterministe, tous les modèles entraînés avec un T donnée, peu importe le moyen (CPU, NJIT ou GPU), seront les mêmes modèles avec les mêmes paramètres.
Rappel: ACC (Accuracy i.e. Précision), F1 (Score F1), FN (Faux Négatif) et FP (Faux Positif).
Rappel : ACC (Accuracy i.e. Précision), F1 (Score F1), FN (Faux Négatif) et FP (Faux Positif).
| Evaluating | ACC (E) | F1 (E) | FN (E) | FP (E) | ACC (T) | F1 (T) | FN (T) | FP (T) |
| ------------------ | ------- | ------ | ------ | ------ | ------- | ------ | ------ | ------ |
@ -202,7 +202,7 @@ L'algorithme de ViolaJones étant déterministe, les fichiers devraient être é
| ViolaJones T = 200 (NJIT) | 3,989,600 | 3ms 989µs 600ns | 15,957,700 | 15ms 957µs 700ns |
| ViolaJones T = 300 (NJIT) | 5,983,900 | 5ms 983µs 900ns | 23,935,500 | 23ms 935µs 500ns |
## Resources additionnels
## Ressources additionnelles
- [Rapid Object Detection using a Boosted Cascade of Simple Features](https://www.cs.cmu.edu/~efros/courses/LBMV07/Papers/viola-cvpr-01.pdf)
- [Chapter 39. Parallel Prefix Sum (Scan) with CUDA](https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing/chapter-39-parallel-prefix-sum-scan-cuda)

View File

@ -1,15 +1,15 @@
FROM nvidia/cuda:12.4.1-devel-ubi9 as builder
FROM nvidia/cuda:12.6.2-devel-ubi9 AS builder
WORKDIR /home/ViolaJones/cpp
COPY *.cu *.cpp *.hpp Makefile ./
RUN make -j "$(nproc)"
RUN make -j "$(nproc)" && make -j "$(nproc)" ./bin/ViolaJonesTest
FROM nvidia/cuda:12.4.1-base-ubi9
FROM nvidia/cuda:12.6.2-base-ubi9
WORKDIR /home/ViolaJones/cpp
RUN dnf install -y make-1:4.3-7.el9 && dnf clean all
RUN dnf install -y make-1:4.3-8.el9 && dnf clean all
COPY --from=builder /home/ViolaJones/cpp/bin ./bin
COPY --from=builder /home/ViolaJones/cpp/Makefile .

View File

@ -8,10 +8,12 @@ DATA_PATH := ../data
#CFLAGS := -O0 -g -G -pg -Xptxas=-w -Xcompiler -O0,-rdynamic,-g
CFLAGS := -dlto -O2 -Xcompiler -O2
#CFLAGS := -dlto -O2 -g -Xcompiler -O2,-g,-ggdb
CFLAGS := $(CFLAGS) -MMD -MP -Werror=all-warnings -Xcompiler -Wall,-Werror,-Werror=implicit-fallthrough=0,-Wextra
CFLAGS := $(CFLAGS) -MMD -MP -Werror=all-warnings -Xcompiler -Wall,-Werror,-Wextra
EXEC := $(OBJ_DIR)/ViolaJones
EXEC_TEST := $(OBJ_DIR)/ViolaJonesTest
DATA := $(DATA_PATH)/X_train.bin $(DATA_PATH)/X_test.bin $(DATA_PATH)/y_train.bin $(DATA_PATH)/y_test.bin
SRC := $(shell find $(SRC_DIR) -name '*.cpp' -o -name '*.cu')
SRC := $(shell find $(SRC_DIR) \( -name '*.cpp' -o -name '*.cu' \) -and -not -name projet_test.cpp)
SRC_TEST := $(shell find $(SRC_DIR) \( -name '*.cpp' -o -name '*.cu' \) -and -not -name projet.cpp)
OBJ_EXT := o
ifeq ($(OS), Windows_NT)
EXEC := $(EXEC).exe
@ -19,6 +21,8 @@ ifeq ($(OS), Windows_NT)
endif
OBJ := $(SRC:$(SRC_DIR)/%.cpp=$(OBJ_DIR)/%.$(OBJ_EXT))
OBJ := $(OBJ:$(SRC_DIR)/%.cu=$(OBJ_DIR)/%.$(OBJ_EXT))
OBJ_TEST := $(SRC_TEST:$(SRC_DIR)/%.cpp=$(OBJ_DIR)/%.$(OBJ_EXT))
OBJ_TEST := $(OBJ_TEST:$(SRC_DIR)/%.cu=$(OBJ_DIR)/%.$(OBJ_EXT))
.PHONY: all
all: $(EXEC)
@ -42,6 +46,12 @@ $(EXEC): $(OBJ)
@echo Linking objects files to $@
@$(CC) $(CFLAGS) $^ -o $@
# FIXME When using the docker image, Make check prequisites even when the target already exists
#$(EXEC_TEST): $(OBJ_TEST) | check-nvcc-works
$(EXEC_TEST): $(OBJ_TEST)
@echo Linking objects files to $@
@$(CC) $(CFLAGS) $^ -o $@
$(DATA):
@echo 'Missing $(DATA) files, use downloader first' && exit 1
@ -49,6 +59,10 @@ $(DATA):
start: $(EXEC) $(DATA)
@./$(EXEC)
.PHONY: test
test: $(EXEC_TEST)
@./$(EXEC_TEST)
.PHONY: debug
debug: $(EXEC) $(DATA)
#@cuda-gdb -q $(EXEC)
@ -63,7 +77,7 @@ check: $(EXEC) $(DATA) | check-valgrind-works
@valgrind -q -s --leak-check=full --show-leak-kinds=all $(EXEC)
.PHONY: cudacheck
cudacheck: $(EXEC) $(DATA) | check-computer-sanitizer-works
cudacheck: $(EXEC) $(DATA) | check-compute-sanitizer-works
@compute-sanitizer --destroy-on-device-error kernel --tool memcheck --leak-check full --report-api-errors all --track-stream-ordered-races all --target-processes all $(EXEC)
#@compute-sanitizer --destroy-on-device-error kernel --tool racecheck --racecheck-detect-level info --racecheck-report all $(EXEC)
#@compute-sanitizer --destroy-on-device-error kernel --tool initcheck --track-unused-memory yes $(EXEC)
@ -88,9 +102,7 @@ log: $(DATA) reset
.PHONY: reset
reset:
@echo 'Deleting generated states and models'
@rm -frv $(OUT_DIR)/* $(MODELS_DIR)/*
#@ln -sv /mnt/pierre_stuffs/ViolaJones/cpp/models .
#@ln -sv /mnt/pierre_stuffs/ViolaJones/cpp/out .
@rm -frv $(OUT_DIR) $(MODELS_DIR)
.PHONY: clean
clean:
@ -130,8 +142,8 @@ check-dot-works:
check-valgrind-works:
@valgrind --version >/dev/null 2>&1 || (echo 'Please install valgrind.' && exit 1)
.PHONY: check-computer-sanitizer-works
check-computer-sanitizer-works:
@computer-sanitizer --version >/dev/null 2>&1 || (echo 'Please install Compute Sanitizer from Cuda toolkit.' && exit 1)
.PHONY: check-compute-sanitizer-works
check-compute-sanitizer-works:
@compute-sanitizer --version >/dev/null 2>&1 || (echo 'Please install Compute Sanitizer from Cuda toolkit.' && exit 1)
-include $(OBJ:.o=.d)

View File

@ -9,7 +9,7 @@
* @tparam T Inner type of the arrays to test
* @param cpu CPU Array
* @param gpu GPU Array
* @return Whether the test was succesful
* @return Whether the test was successful
*/
template <typename T>
bool unit_test_cpu_vs_gpu(const np::Array<T>& cpu, const np::Array<T>& gpu) noexcept {
@ -249,7 +249,7 @@ np::Array<uint8_t> classify_viola_jones(const np::Array<float64_t>&, const np::A
np::Array<float64_t> init_weights(const np::Array<uint8_t>&) noexcept;
/**
* @brief Select the best classifer given their predictions.
* @brief Select the best classifier given their predictions.
*
* @param classifiers The weak classifiers
* @param weights Trained weights of each classifiers
@ -261,7 +261,7 @@ std::tuple<int32_t, float64_t, np::Array<float64_t>> select_best(const np::Array
const np::Array<uint8_t>&) noexcept;
/**
* @brief Train the weak calssifiers.
* @brief Train the weak classifiers.
*
* @param T Number of weak classifiers
* @param X_feat Integrated features

View File

@ -1,6 +1,7 @@
services:
violajones-cpp:
image: saundersp/violajones-cpp
pull_policy: never
build: .
volumes:
- ./models:/home/ViolaJones/cpp/models
@ -11,5 +12,4 @@ services:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]

31
cpp/projet_test.cpp Normal file
View File

@ -0,0 +1,31 @@
#include "toolbox.hpp"
#include "config.hpp"
#include "toolbox_unit_test.hpp"
#include "ViolaJones.hpp"
#if GPU_BOOSTED
#include "gpu_unit_test.hpp"
#endif
int32_t main(void){
setlocale(LC_NUMERIC, ""); // Allow proper number display
const std::chrono::system_clock::time_point unit_timestamp = perf_counter_ns();
const std::array<int32_t, 3> unit_gaps = { 27, -18, 29 };
header(unit_gaps, { "Unit testing", "Time spent (ns)", "Formatted time spent" });
#if GPU_BOOSTED
benchmark_function_void("Testing GPU capabilities 1D", unit_gaps[0], test_working, 50000);
benchmark_function_void("Testing GPU capabilities 2D", unit_gaps[0], test_working_2d, 200, 500);
benchmark_function_void("Testing GPU capabilities 3D", unit_gaps[0], test_working_3d, 30, 40, 500);
#endif
benchmark_function_void("Testing format_time", unit_gaps[0], format_time_test);
benchmark_function_void("Testing format_time_ns", unit_gaps[0], format_time_ns_test);
benchmark_function_void("Testing format_byte_size", unit_gaps[0], format_byte_size_test);
benchmark_function_void("Testing thousand_sep", unit_gaps[0], thousand_sep_test);
const long long time_spent = duration_ns(perf_counter_ns() - unit_timestamp);
formatted_line(unit_gaps, "", "", "", "");
formatted_row(unit_gaps, { "Unit testing summary", thousand_sep(time_spent).c_str(), format_time_ns(time_spent).c_str() });
footer(unit_gaps);
return EXIT_SUCCESS;
}

View File

@ -1,62 +0,0 @@
#include <iostream>
#include <iomanip>
#include "data.hpp"
#include "toolbox.hpp"
#define PBSTR "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
#define PBWIDTH 60
void printProgress(const float64_t& percentage) noexcept {
const uint64_t val = static_cast<uint64_t>(percentage * 100);
const int32_t lpad = static_cast<int32_t>(percentage * PBWIDTH);
const int32_t rpad = PBWIDTH - lpad;
fprintf(stderr, "%3lu%% [%.*s%*s]\r", val, lpad, PBSTR, rpad, "");
fflush(stderr);
}
void clearProgress(void) noexcept {
// Progress bar width + space before + num space + space after
fprintf(stderr, "%*c\r", PBWIDTH + 1 + 3 + 3, ' ');
}
template<typename T>
void test(const uint64_t& N) noexcept {
#if __DEBUG
printf("DETERMINISTIC for N=%s of %s sized %s\n", thousand_sep(N).c_str(), typeid(T).name(), format_byte_size(sizeof(T)).c_str());
printf("Estimating memory footprint at : %s\n", format_byte_size(3 * N * sizeof(T)).c_str());
#endif
T *a = new T[N], *b = new T[N], *c = new T[N];
T mean = static_cast<T>(0.0);
const size_t percent = N / 100;
for(size_t i = 0; i < N; ++i){
if (i % percent == 0) printProgress(static_cast<float64_t>(i) / N);
a[i] = static_cast<T>(i < N>>1 ? 0.1 : 1.0);
b[i] = static_cast<T>(1.0);
c[i] = a[i] * b[i];
mean += c[i];
}
mean /= static_cast<T>(N);
clearProgress();
std::cout << mean << std::endl;
delete[] a, delete[] b, delete[] c;
}
void test_float(void) noexcept {
std::cout << std::setprecision(1<<8);
const uint64_t N = static_cast<uint64_t>(1)<<28;
test<float128_t>(N);
test<float64_t>(N);
test<float32_t>(N);
//printf("%.128af\n", static_cast<float64_t>(1) / 3);
//std::cout << static_cast<float64_t>(1) / 3 << std::endl;
//std::cout << std::hexfloat << static_cast<float64_t>(1) / 3 << std::endl;
//printf("%.128Lf\n", static_cast<long float64_t>(1) / 3);
//printf("%.128lf\n", static_cast<float64_t>(1) / 3);
//printf("%.128f\n", static_cast<float>(1) / 3);
}

View File

@ -1,6 +1,6 @@
FROM alpine:3.19.1
FROM alpine:3.20.3
RUN apk add --no-cache curl=8.5.0-r0 python3=3.11.9-r0 && rm -rf /var/cache/apk*
RUN apk add --no-cache curl=8.11.0-r1 python3=3.12.7-r0 && rm -rf /var/cache/apk*
WORKDIR /home/ViolaJones/downloader
COPY requirements.txt activate.sh ./

View File

@ -4,16 +4,14 @@
set -e
test -z "$EXEC_DIR" && EXEC_DIR=.
test -z "$VENV_PATH" && VENV_PATH="$EXEC_DIR/venv"
test -z "$VENV_PATH" && VENV_PATH="$EXEC_DIR/.venv"
activate(){
if [ ! -d "$VENV_PATH" ]; then
echo 'Creating python virtual environnement'
python -m venv "$VENV_PATH"
python -m venv --upgrade-deps "$VENV_PATH"
echo 'Activating virtual environnement'
activate
echo 'Updating base pip packages'
python -m pip install -U setuptools pip
echo 'Installing requirements'
pip install -r requirements.txt
elif [ -f "$VENV_PATH"/Scripts/activate ]; then . "$VENV_PATH"/Scripts/activate

View File

@ -3,29 +3,31 @@ from tqdm import tqdm
from functools import partial
from sys import argv
import numpy as np
from numpy.typing import NDArray
from typing import Final, Callable
from os import path, listdir
# Induce determinism
np.random.seed(133742)
np.random.seed(196_863)
# Makes the "leave" argument default to False
tqdm = partial(tqdm, leave = False)
# Makes the 'leave' argument default to False
tqdm: Callable = partial(tqdm, leave = False)
def read_pgm(pgm_file: BufferedReader) -> np.ndarray:
def read_pgm(pgm_file: BufferedReader) -> NDArray[np.uint8]:
"""Read the data of a PGM file
Args:
pgm_file (BufferedReader): PGM File
Returns:
np.ndarray: PGM data
NDArray[np.uint8]: PGM data
"""
assert (f := pgm_file.readline()) == b'P5\n', f"Incorrect file format: {f}"
(width, height) = [int(i) for i in pgm_file.readline().split()]
assert width > 0 and height > 0, f"Incorrect dimensions: {width}x{height}"
assert (depth := int(pgm_file.readline())) < 256, f"Incorrect depth: {depth}"
assert (f := pgm_file.readline()) == b'P5\n', f'Incorrect file format: {f}'
(width, height) = (int(i) for i in pgm_file.readline().split())
assert width > 0 and height > 0, f'Incorrect dimensions: {width}x{height}'
assert (depth := int(pgm_file.readline())) < 256, f'Incorrect depth: {depth}'
buff = np.empty(height * width, dtype = np.uint8)
buff: Final[NDArray[np.uint8]] = np.empty(height * width, dtype = np.uint8)
for i in range(buff.shape[0]):
buff[i] = ord(pgm_file.read(1))
return buff.reshape((height, width))
@ -36,25 +38,25 @@ def __main__(data_path: str) -> None:
Args:
data_path (str): Path of the PGM files
"""
for set_name in tqdm(["train", "test"], desc = "set name"):
for set_name in tqdm(['train', 'test'], desc = 'set name'):
X, y = [], []
for y_i, label in enumerate(tqdm(["non-face", "face"], desc = "label")):
for filename in tqdm(listdir(f"{data_path}/{set_name}/{label}"), desc = "Reading pgm file"):
with open(f"{data_path}/{set_name}/{label}/{filename}", "rb") as face:
for y_i, label in enumerate(tqdm(['non-face', 'face'], desc = 'label')):
for filename in tqdm(listdir(f'{data_path}/{set_name}/{label}'), desc = 'Reading pgm file'):
with open(f'{data_path}/{set_name}/{label}/{filename}', 'rb') as face:
X.append(read_pgm(face))
y.append(y_i)
X, y = np.asarray(X), np.asarray(y)
idx = np.random.permutation(y.shape[0])
idx: NDArray[np.int64] = np.random.permutation(y.shape[0])
X, y = X[idx], y[idx]
for org, s in tqdm(zip("Xy", [X, y]), desc = f"Writing {set_name}"):
with open(f"{data_path}/{org}_{set_name}.bin", "w") as out:
out.write(f'{str(s.shape)[1:-1].replace(",", "")}\n')
raw = s.ravel()
for s_i in tqdm(raw[:-1], desc = f"Writing {org}"):
out.write(f"{s_i} ")
for org, s in tqdm(zip('Xy', [X, y]), desc = f'Writing {set_name}'):
with open(f'{data_path}/{org}_{set_name}.bin', 'w') as out:
out.write(f'{str(s.shape)[1:-1].replace(',', '')}\n')
raw: NDArray = s.ravel()
for s_i in tqdm(raw[:-1], desc = f'Writing {org}'):
out.write(f'{s_i} ')
out.write(str(raw[-1]))
if __name__ == "__main__":
__main__(argv[1]) if len(argv) == 2 else print(f"Usage: python {__file__[__file__.rfind(path.sep) + 1:]} ./data_location")
if __name__ == '__main__':
__main__(argv[1]) if len(argv) == 2 else print(f'Usage: python {__file__[__file__.rfind(path.sep) + 1:]} ./data_location')

View File

@ -1,6 +1,7 @@
services:
downloader:
image: saundersp/violajones-downloader
pull_policy: never
build: .
volumes:
- ../data:/home/ViolaJones/data

View File

@ -1,2 +1,2 @@
numpy==1.26.4
tqdm==4.66.2
numpy==2.1.3
tqdm==4.67.0

View File

@ -1,7 +1,8 @@
FROM nvidia/cuda:12.4.1-devel-ubi9 as builder
FROM nvidia/cuda:12.6.2-devel-ubi9
RUN dnf install -y python3.11-3.11.5-1.el9_3 && dnf clean all
RUN ln -s /usr/bin/python3 /usr/bin/python
RUN dnf install -y python3.12-3.12.1-4.el9_4.4 \
&& dnf clean all \
&& ln -s /usr/bin/python3.12 /usr/bin/python
WORKDIR /home/ViolaJones/python
COPY Makefile activate.sh requirements.txt ./

View File

@ -17,6 +17,10 @@ venv:
start: $(DATA) | venv check-python-works
@sh -c '. ./activate.sh && python projet.py'
.PHONY: test
test: | venv check-python-works
@sh -c '. ./activate.sh && python project_test.py'
.PHONY: debug
debug: $(DATA) | venv check-python-works check-pudb-works
@bash -c 'source activate.sh && pudb projet.py'
@ -45,9 +49,7 @@ log: $(DATA) reset | venv
.PHONY: reset
reset:
@echo 'Deleting generated states and models'
@rm -frv $(OUT_DIR)/* $(MODELS_DIR)/*
#@ln -sv /mnt/pierre_stuffs/ViolaJones/python/models .
#@ln -sv /mnt/pierre_stuffs/ViolaJones/python/out .
@rm -frv $(OUT_DIR) $(MODELS_DIR)
.PHONY: clean
clean:
@ -55,7 +57,7 @@ clean:
.PHONY: mrproper
mrproper: clean
@rm -rfv __pycache__ venv
@rm -rfv __pycache__ .venv
.PHONY: help
help:

View File

@ -4,16 +4,14 @@
set -e
test -z "$EXEC_DIR" && EXEC_DIR=.
test -z "$VENV_PATH" && VENV_PATH="$EXEC_DIR/venv"
test -z "$VENV_PATH" && VENV_PATH="$EXEC_DIR/.venv"
activate(){
if [ ! -d "$VENV_PATH" ]; then
echo 'Creating python virtual environnement'
python -m venv "$VENV_PATH"
python -m venv --upgrade-deps "$VENV_PATH"
echo 'Activating virtual environnement'
activate
echo 'Updating base pip packages'
python -m pip install -U setuptools pip
echo 'Installing requirements'
pip install -r requirements.txt
elif [ -f "$VENV_PATH"/Scripts/activate ]; then . "$VENV_PATH"/Scripts/activate

View File

@ -1,6 +1,7 @@
services:
violajones-python:
image: saundersp/violajones-python
pull_policy: never
build: .
volumes:
- ./models:/home/ViolaJones/python/models
@ -11,5 +12,4 @@ services:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]

15
python/project_test.py Normal file
View File

@ -0,0 +1,15 @@
from toolbox_unit_test import format_time_test, format_time_ns_test
from toolbox import header, footer, formatted_row, formatted_line
from toolbox import format_time_ns, benchmark_function
from time import perf_counter_ns
if __name__ == '__main__':
unit_timestamp = perf_counter_ns()
unit_gaps = [27, -18, 29]
header(unit_gaps, ['Unit testing', 'Time spent (ns)', 'Formatted time spent'])
benchmark_function('testing format_time', unit_gaps[0], format_time_test)
benchmark_function('testing format_time_ns', unit_gaps[0], format_time_ns_test)
time_spent = perf_counter_ns() - unit_timestamp
formatted_line(unit_gaps, '', '', '', '')
formatted_row(unit_gaps, ['Unit testing summary', f'{time_spent:,}', format_time_ns(time_spent)])
footer(unit_gaps)

View File

@ -1,3 +1,5 @@
numba==0.59.1
scikit-learn==1.4.1.post1
tqdm==4.66.2
numba==0.60.0
scikit-learn==1.5.2
tqdm==4.67.0
#pudb==2024.1.3

View File

@ -1,5 +1,5 @@
from typing import Any
from toolbox import format_time, format_time_ns
from typing import Any
def Assert(name: str, expected: Any, result: Any):
"""Test if a given result is equal of the expected one and log result
@ -18,115 +18,116 @@ def format_time_test() -> None:
See https://en.wikipedia.org/wiki/Unit_of_time for details
"""
Assert("format_time null", "0s", format_time(0))
Assert("format_time second", "1s", format_time(1))
Assert("format_time decasecond", "10s", format_time(10))
Assert("format_time minute", "1m", format_time(60))
Assert("format_time milliday", "1m 26s", format_time(86)) # missing 0.4s due to precision
Assert("format_time hectosecond", "1m 40s", format_time(100))
Assert("format_time kilosecond", "16m 40s", format_time(int(1e3)))
Assert("format_time hour", "1h", format_time(3600))
Assert("format_time day", "1j", format_time(86400))
Assert("format_time week/sennight", "1w", format_time(604800))
Assert("format_time megasecond", "1w 4j 13h 46m 40s", format_time(int(1e6)))
Assert("format_time fortnight", "2w", format_time(1209600))
Assert("format_time lunar month (draconitic)", "3w 6j 5h 5m 35s", format_time(2351135)) # missing 0.8 due to precision
Assert("format_time lunar month (tropical)", "3w 6j 7h 43m 4s", format_time(2360584)) # missing 0.7 due to precision
Assert("format_time lunar month (sidereal)", "3w 6j 7h 43m 11s", format_time(2360591)) # missing 0.6 to precision
Assert("format_time lunar month (anomalistic)", "3w 6j 13h 18m 33s", format_time(2380713)) # missing 0.2 due to precision
Assert("format_time lunar month (synodic)", "4w 1j 12h 44m 2s", format_time(2551442)) # missing 0.9 due to precision
Assert("format_time month", "1M", format_time(2678400))
Assert("format_time quarantine", "1M 1w 2j", format_time(int(3456e3)))
Assert("format_time semester", "4M 2j", format_time(10886400))
Assert("format_time lunar year", "11M 1w 6j 8h 52m 48s", format_time(30617568))
Assert("format_time year", "1y", format_time(int(31536e3)))
Assert("format_time tropical year", "1y 5h 48m 45s", format_time(31556925)) # missing 0.216 due to precision
Assert("format_time gregorian year", "1y 5h 49m 12s", format_time(31556952))
Assert("format_time sidereal year", "1y 6h 9m 9s", format_time(31558149)) # missing 0.7635456 due to precision
Assert("format_time leap year", "1y 1j", format_time(31622400))
Assert("format_time olympiad", "4y", format_time(int(126144e3)))
Assert("format_time lusturm", "5y", format_time(int(15768e4)))
Assert("format_time decade", "10y", format_time(int(31536e4)))
Assert("format_time indiction", "15y", format_time(int(47304e4)))
Assert("format_time score", "20y", format_time(int(63072e4)))
Assert("format_time gigasecond", "31y 8M 1w 4j 1h 46m 40s", format_time(int(1e9)))
Assert("format_time jubilee", "50y", format_time(int(15768e5)))
Assert("format_time century", "1c", format_time(int(31536e5)))
Assert("format_time millennium", "10c", format_time(int(31536e6)))
Assert("format_time age", "257c 72y", format_time(int(812745792e3)))
Assert("format_time terasecond", "3170c 97y 10M 3w 4j 17h 46m 40s", format_time(int(1e13)))
Assert("format_time megaannum", "10000c", format_time(int(31536e9)))
Assert("format_time petasecond", "317097c 91y 11M 2w 4j 1h 46m 40s", format_time(int(1e15)))
Assert("format_time galactic year", "2300000c", format_time(int(725328e10)))
Assert("format_time eon", "10000000c", format_time(int(31536e12)))
Assert("format_time kalpa", "43200000c", format_time(int(13623552e10)))
Assert("format_time exasecond", "317097919c 83y 9M 1h 46m 40s", format_time(int(1e18)))
Assert('format_time null', '0s', format_time(0))
Assert('format_time second', '1s', format_time(1))
Assert('format_time decasecond', '10s', format_time(10))
Assert('format_time minute', '1m', format_time(60))
Assert('format_time milliday', '1m 26s', format_time(86)) # missing 0.4s due to precision
Assert('format_time hectosecond', '1m 40s', format_time(100))
Assert('format_time kilosecond', '16m 40s', format_time(int(1e3)))
Assert('format_time hour', '1h', format_time(3600))
Assert('format_time day', '1j', format_time(86400))
Assert('format_time week/sennight', '1w', format_time(604800))
Assert('format_time megasecond', '1w 4j 13h 46m 40s', format_time(int(1e6)))
Assert('format_time fortnight', '2w', format_time(1209600))
Assert('format_time lunar month (draconitic)', '3w 6j 5h 5m 35s', format_time(2351135)) # missing 0.8 due to precision
Assert('format_time lunar month (tropical)', '3w 6j 7h 43m 4s', format_time(2360584)) # missing 0.7 due to precision
Assert('format_time lunar month (sidereal)', '3w 6j 7h 43m 11s', format_time(2360591)) # missing 0.6 to precision
Assert('format_time lunar month (anomalistic)', '3w 6j 13h 18m 33s', format_time(2380713)) # missing 0.2 due to precision
Assert('format_time lunar month (synodic)', '4w 1j 12h 44m 2s', format_time(2551442)) # missing 0.9 due to precision
Assert('format_time month', '1M', format_time(2678400))
Assert('format_time quarantine', '1M 1w 2j', format_time(int(3456e3)))
Assert('format_time semester', '4M 2j', format_time(10886400))
Assert('format_time lunar year', '11M 1w 6j 8h 52m 48s', format_time(30617568))
Assert('format_time year', '1y', format_time(int(31536e3)))
Assert('format_time tropical year', '1y 5h 48m 45s', format_time(31556925)) # missing 0.216 due to precision
Assert('format_time gregorian year', '1y 5h 49m 12s', format_time(31556952))
Assert('format_time sidereal year', '1y 6h 9m 9s', format_time(31558149)) # missing 0.7635456 due to precision
Assert('format_time leap year', '1y 1j', format_time(31622400))
Assert('format_time olympiad', '4y', format_time(int(126144e3)))
Assert('format_time lusturm', '5y', format_time(int(15768e4)))
Assert('format_time decade', '10y', format_time(int(31536e4)))
Assert('format_time indiction', '15y', format_time(int(47304e4)))
Assert('format_time score', '20y', format_time(int(63072e4)))
Assert('format_time gigasecond', '31y 8M 1w 4j 1h 46m 40s', format_time(int(1e9)))
Assert('format_time jubilee', '50y', format_time(int(15768e5)))
Assert('format_time century', '1c', format_time(int(31536e5)))
Assert('format_time millennium', '10c', format_time(int(31536e6)))
Assert('format_time age', '257c 72y', format_time(int(812745792e3)))
Assert('format_time terasecond', '3170c 97y 10M 3w 4j 17h 46m 40s', format_time(int(1e13)))
Assert('format_time megaannum', '10000c', format_time(int(31536e9)))
Assert('format_time petasecond', '317097c 91y 11M 2w 4j 1h 46m 40s', format_time(int(1e15)))
Assert('format_time galactic year', '2300000c', format_time(int(725328e10)))
Assert('format_time eon', '10000000c', format_time(int(31536e12)))
Assert('format_time kalpa', '43200000c', format_time(int(13623552e10)))
Assert('format_time exasecond', '317097919c 83y 9M 1h 46m 40s', format_time(int(1e18)))
# Cannot use number bigger than currently supported ISO Python
#Assert("format_time zettasecond", "", format_time(1e21))
#Assert("format_time yottasecond", "", format_time(1e24))
#Assert("format_time ronnasecond", "", format_time(1e27))
#Assert("format_time quettasecond", "", format_time(1e30))
# Assert('format_time zettasecond', '', format_time(1e21))
# Assert('format_time yottasecond', '', format_time(1e24))
# Assert('format_time ronnasecond', '', format_time(1e27))
# Assert('format_time quettasecond', '', format_time(1e30))
# uint64_t_MAX == 2**64 == 18446744073709551615 == -1
Assert("format_time max", "5849424173c 55y 3w 5j 7h 16s", format_time(int(2**64 - 1)))
Assert('format_time max', '5849424173c 55y 3w 5j 7h 16s', format_time(int(2**64 - 1)))
def format_time_ns_test() -> None:
"""Test suite for the format_time_ns output
See https://en.wikipedia.org/wiki/Unit_of_time for details
"""
Assert("format_time_ns null", "0ns", format_time_ns(0))
Assert("format_time_ns nanosecond", "1ns", format_time_ns(1))
Assert("format_time_ns shake", "10ns", format_time_ns(10))
Assert("format_time_ns microsecond", "1µs", format_time_ns(int(1e3)))
Assert("format_time_ns millisecond", "1ms", format_time_ns(int(1e6)))
Assert("format_time_ns centisecond", "10ms", format_time_ns(int(1e7)))
Assert("format_time_ns decisecond", "100ms", format_time_ns(int(1e8)))
Assert("format_time_ns second", "1s", format_time_ns(int(1e9)))
Assert("format_time_ns decasecond", "10s", format_time_ns(int(1e10)))
Assert("format_time_ns minute", "1m", format_time_ns(int(6e10)))
Assert("format_time_ns milliday", "1m 26s 400ms", format_time_ns(int(864e8)))
Assert("format_time_ns hectosecond", "1m 40s", format_time_ns(int(1e11)))
Assert("format_time_ns kilosecond", "16m 40s", format_time_ns(int(1e12)))
Assert("format_time_ns hour", "1h", format_time_ns(int(36e11)))
Assert("format_time_ns day", "1j", format_time_ns(int(864e11)))
Assert("format_time_ns week/sennight", "1w", format_time_ns(int(6048e11)))
Assert("format_time_ns megasecond", "1w 4j 13h 46m 40s", format_time_ns(int(1e15)))
Assert("format_time_ns fortnight", "2w", format_time_ns(int(12096e11)))
Assert("format_time_ns lunar month (draconitic)", "3w 6j 5h 5m 35s 800ms", format_time_ns(int(23511358e8)))
Assert("format_time_ns lunar month (tropical)", "3w 6j 7h 43m 4s 700ms", format_time_ns(int(23605847e8)))
Assert("format_time_ns lunar month (sidereal)", "3w 6j 7h 43m 11s 600ms", format_time_ns(int(23605916e8)))
Assert("format_time_ns lunar month (anomalistic)", "3w 6j 13h 18m 33s 200ms", format_time_ns(int(23807132e8)))
Assert("format_time_ns lunar month (synodic)", "4w 1j 12h 44m 2s 900ms", format_time_ns(int(25514429e8)))
Assert("format_time_ns month", "1M", format_time_ns(int(26784e11)))
Assert("format_time_ns quarantine", "1M 1w 2j", format_time_ns(int(3456e12)))
Assert("format_time_ns semester", "4M 2j", format_time_ns(int(108864e11)))
Assert("format_time_ns lunar year", "11M 1w 6j 8h 52m 48s", format_time_ns(int(30617568e9)))
Assert("format_time_ns year", "1y", format_time_ns(int(31536e12)))
Assert("format_time_ns tropical year", "1y 5h 48m 45s 216ms", format_time_ns(int(31556925216e6)))
Assert("format_time_ns gregorian year", "1y 5h 49m 12s", format_time_ns(int(31556952e9)))
Assert("format_time_ns sidereal year", "1y 6h 9m 9s 763ms 545µs 600ns", format_time_ns(int(315581497635456e2)))
Assert("format_time_ns leap year", "1y 1j", format_time_ns(int(316224e11)))
Assert("format_time_ns olympiad", "4y", format_time_ns(int(126144e12)))
Assert("format_time_ns lusturm", "5y", format_time_ns(int(15768e13)))
Assert("format_time_ns decade", "10y", format_time_ns(int(31536e13)))
Assert("format_time_ns indiction", "15y", format_time_ns(int(47304e13)))
Assert("format_time_ns score", "20y", format_time_ns(int(63072e13)))
Assert("format_time_ns gigasecond", "31y 8M 1w 4j 1h 46m 40s", format_time_ns(int(1e18)))
Assert("format_time_ns jubilee", "50y", format_time_ns(int(15768e14)))
Assert("format_time_ns century", "1c", format_time_ns(int(31536e14)))
Assert("format_time_ns millennium", "10c", format_time_ns(int(31536e15)))
Assert("format_time_ns age", "257c 72y", format_time_ns(int(812745792e12)))
Assert("format_time_ns terasecond", "3170c 97y 10M 3w 4j 17h 46m 40s", format_time_ns(int(1e22)))
Assert("format_time_ns megaannum", "10000c", format_time_ns(int(31536e18)))
Assert('format_time_ns null', '0ns', format_time_ns(0))
Assert('format_time_ns nanosecond', '1ns', format_time_ns(1))
Assert('format_time_ns shake', '10ns', format_time_ns(10))
Assert('format_time_ns microsecond', '1µs', format_time_ns(int(1e3)))
Assert('format_time_ns millisecond', '1ms', format_time_ns(int(1e6)))
Assert('format_time_ns centisecond', '10ms', format_time_ns(int(1e7)))
Assert('format_time_ns decisecond', '100ms', format_time_ns(int(1e8)))
Assert('format_time_ns second', '1s', format_time_ns(int(1e9)))
Assert('format_time_ns decasecond', '10s', format_time_ns(int(1e10)))
Assert('format_time_ns minute', '1m', format_time_ns(int(6e10)))
Assert('format_time_ns milliday', '1m 26s 400ms', format_time_ns(int(864e8)))
Assert('format_time_ns hectosecond', '1m 40s', format_time_ns(int(1e11)))
Assert('format_time_ns kilosecond', '16m 40s', format_time_ns(int(1e12)))
Assert('format_time_ns hour', '1h', format_time_ns(int(36e11)))
Assert('format_time_ns day', '1j', format_time_ns(int(864e11)))
Assert('format_time_ns week/sennight', '1w', format_time_ns(int(6048e11)))
Assert('format_time_ns megasecond', '1w 4j 13h 46m 40s', format_time_ns(int(1e15)))
Assert('format_time_ns fortnight', '2w', format_time_ns(int(12096e11)))
Assert('format_time_ns lunar month (draconitic)', '3w 6j 5h 5m 35s 800ms', format_time_ns(int(23511358e8)))
Assert('format_time_ns lunar month (tropical)', '3w 6j 7h 43m 4s 700ms', format_time_ns(int(23605847e8)))
Assert('format_time_ns lunar month (sidereal)', '3w 6j 7h 43m 11s 600ms', format_time_ns(int(23605916e8)))
Assert('format_time_ns lunar month (anomalistic)', '3w 6j 13h 18m 33s 200ms', format_time_ns(int(23807132e8)))
Assert('format_time_ns lunar month (synodic)', '4w 1j 12h 44m 2s 900ms', format_time_ns(int(25514429e8)))
Assert('format_time_ns month', '1M', format_time_ns(int(26784e11)))
Assert('format_time_ns quarantine', '1M 1w 2j', format_time_ns(int(3456e12)))
Assert('format_time_ns semester', '4M 2j', format_time_ns(int(108864e11)))
Assert('format_time_ns lunar year', '11M 1w 6j 8h 52m 48s', format_time_ns(int(30617568e9)))
Assert('format_time_ns year', '1y', format_time_ns(int(31536e12)))
Assert('format_time_ns tropical year', '1y 5h 48m 45s 216ms', format_time_ns(int(31556925216e6)))
Assert('format_time_ns gregorian year', '1y 5h 49m 12s', format_time_ns(int(31556952e9)))
Assert('format_time_ns sidereal year', '1y 6h 9m 9s 763ms 545µs 600ns', format_time_ns(int(315581497635456e2)))
Assert('format_time_ns leap year', '1y 1j', format_time_ns(int(316224e11)))
Assert('format_time_ns olympiad', '4y', format_time_ns(int(126144e12)))
Assert('format_time_ns lusturm', '5y', format_time_ns(int(15768e13)))
Assert('format_time_ns decade', '10y', format_time_ns(int(31536e13)))
Assert('format_time_ns indiction', '15y', format_time_ns(int(47304e13)))
Assert('format_time_ns score', '20y', format_time_ns(int(63072e13)))
Assert('format_time_ns gigasecond', '31y 8M 1w 4j 1h 46m 40s', format_time_ns(int(1e18)))
Assert('format_time_ns jubilee', '50y', format_time_ns(int(15768e14)))
Assert('format_time_ns century', '1c', format_time_ns(int(31536e14)))
# Python int too large to convert to C long
# Assert('format_time_ns millennium', '10c', format_time_ns(int(31536e15)))
# Assert('format_time_ns age', '257c 72y', format_time_ns(int(812745792e12)))
# Assert('format_time_ns terasecond', '3170c 97y 10M 3w 4j 17h 46m 40s', format_time_ns(int(1e22)))
# Assert('format_time_ns megaannum', '10000c', format_time_ns(int(31536e18)))
# Cannot use number bigger than currently supported ISO Python
# Assert("format_time_ns petasecond", "317097c 91y 11M 2w 4j 1h 46m 40s", format_time_ns(int(1e24)))
# Assert("format_time_ns galactic year", "2300000c", format_time_ns(int(725328e19)))
# Assert("format_time_ns eon", "10000000c", format_time_ns(int(31536e21)))
# Assert("format_time_ns kalpa", "43200000c", format_time_ns(int(13623552e19)))
# Assert("format_time_ns exasecond", "317097919c 83y 9M 1h 46m 40s", format_time_ns(int(1e27)))
# Assert("format_time_ns zettasecond", "", format_time_ns(int(1e30)))
# Assert("format_time_ns yottasecond", "", format_time_ns(int(1e33)))
# Assert("format_time_ns ronnasecond", "", format_time_ns(int(1e36)))
# Assert("format_time_ns quettasecond", "", format_time_ns(int(1e39)))
# Assert('format_time_ns petasecond', '317097c 91y 11M 2w 4j 1h 46m 40s', format_time_ns(int(1e24)))
# Assert('format_time_ns galactic year', '2300000c', format_time_ns(int(725328e19)))
# Assert('format_time_ns eon', '10000000c', format_time_ns(int(31536e21)))
# Assert('format_time_ns kalpa', '43200000c', format_time_ns(int(13623552e19)))
# Assert('format_time_ns exasecond', '317097919c 83y 9M 1h 46m 40s', format_time_ns(int(1e27)))
# Assert('format_time_ns zettasecond', '', format_time_ns(int(1e30)))
# Assert('format_time_ns yottasecond', '', format_time_ns(int(1e33)))
# Assert('format_time_ns ronnasecond', '', format_time_ns(int(1e36)))
# Assert('format_time_ns quettasecond', '', format_time_ns(int(1e39)))
# uint64_t_MAX == 2**64 == 18446744073709551615 == -1
Assert("format_time_ns max", "5c 84y 11M 2j 23h 34m 33s 709ms 551µs 615ns", format_time_ns(2**64 - 1))
Assert('format_time_ns max', '5c 84y 11M 2j 23h 34m 33s 709ms 551µs 615ns', format_time_ns(2**64 - 1))