From 42124261bc3f871861add382e830de455c7b3243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kowalczyk?= Date: Fri, 17 Apr 2026 12:45:14 +0200 Subject: [PATCH 1/5] changed generate report script to allow jupyter notebooks and added dependencies --- generate_report.py | 35 +++++++++++++++++++++++++++++------ pyproject.toml | 2 +- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/generate_report.py b/generate_report.py index c48e260..7336337 100755 --- a/generate_report.py +++ b/generate_report.py @@ -7,6 +7,8 @@ from pathlib import Path import markdown # type: ignore +import nbformat +from nbconvert import HTMLExporter from weasyprint import HTML @@ -22,6 +24,19 @@ def md_to_pdf(md_path: Path, pdf_path: Path): HTML(string=html, base_url=md_path.parent).write_pdf(str(pdf_path)) +def ipynb_to_pdf(ipynb_path: Path, pdf_path: Path): + """Convert Jupyter notebook to PDF via HTML""" + with open(ipynb_path, encoding="utf-8") as f: + nb = nbformat.read(f, as_version=4) + + html_exporter = HTMLExporter() + html_exporter.exclude_input_prompt = True + html_exporter.exclude_output_prompt = True + body, _ = html_exporter.from_notebook_node(nb) + + HTML(string=body, base_url=ipynb_path.parent).write_pdf(str(pdf_path)) + + def resolve_module_path(module_name): """Resolve module path using importlib""" spec = importlib.util.find_spec(module_name) @@ -97,22 +112,30 @@ def export_code(entry_file: Path, output_txt: Path): def main(): parser = argparse.ArgumentParser(description="Generate project report.") parser.add_argument("--description_file", "-d", required=True) - parser.add_argument("--code", "-c", required=True) + parser.add_argument("--code", "-c", required=False, default=None) parser.add_argument("--output_dir", "-o", default="report") args = parser.parse_args() - desc_path = Path(args.description_file) - code_path = Path(args.code) output_dir = Path(args.output_dir) - output_dir.mkdir(parents=True, exist_ok=True) pdf_output = output_dir / "report.pdf" txt_output = output_dir / "code.txt" - md_to_pdf(desc_path, pdf_output) - export_code(code_path, txt_output) + desc_path = Path(args.description_file) + + if desc_path.suffix == ".md": + md_to_pdf(desc_path, pdf_output) + elif desc_path.suffix == ".ipynb": + ipynb_to_pdf(desc_path, pdf_output) + else: + raise ValueError(f"Unsupported file type: {desc_path.suffix}") + + if args.code is not None: + code_path = Path(args.code) + + export_code(code_path, txt_output) if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index f6d3e78..6946fe3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "matrix_calculus" version = "1.5.0" description = "Matrix calculus toolkit" requires-python = ">=3.10" -dependencies = ["numpy", "seaborn", "matplotlib", "weasyprint", "markdown"] +dependencies = ["numpy", "seaborn", "matplotlib", "weasyprint", "markdown", "notebook", "nbconvert"] [tool.setuptools.packages.find] where = ["."] From 540e4a3b61a8c3a59875dcbc69208f5b9b7ac3f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kowalczyk?= Date: Fri, 17 Apr 2026 12:46:25 +0200 Subject: [PATCH 2/5] added initial version of matrix norms notebook --- notebooks/matrix_norms.ipynb | 56 +++++++++++++++++++++++++++++++++++ notebooks/report.pdf | Bin 0 -> 5553 bytes 2 files changed, 56 insertions(+) create mode 100644 notebooks/matrix_norms.ipynb create mode 100644 notebooks/report.pdf diff --git a/notebooks/matrix_norms.ipynb b/notebooks/matrix_norms.ipynb new file mode 100644 index 0000000..6cce2e6 --- /dev/null +++ b/notebooks/matrix_norms.ipynb @@ -0,0 +1,56 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a65bf3b0", + "metadata": {}, + "source": [ + "# Normy macierzowe" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "72cbd32f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-2.0" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import matrix_calculus.linalg as linalg\n", + "\n", + "linalg.det([[1,2],[3, 4]])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.20" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/report.pdf b/notebooks/report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c7bb0395a1374a0401f857317594f858232ab1dd GIT binary patch literal 5553 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 literal 0 HcmV?d00001 From e3c852b1a276685ba8c975d71f73ffec2ee16d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kowalczyk?= Date: Fri, 17 Apr 2026 19:55:26 +0200 Subject: [PATCH 3/5] changed notebooks subdir structure --- .../{report.pdf => resources/matrix_norms.pdf} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename notebooks/{report.pdf => resources/matrix_norms.pdf} (100%) diff --git a/notebooks/report.pdf b/notebooks/resources/matrix_norms.pdf similarity index 100% rename from notebooks/report.pdf rename to notebooks/resources/matrix_norms.pdf From f57606ec680e602bbaa13fa63eb381c70355d7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kowalczyk?= Date: Fri, 17 Apr 2026 20:29:29 +0200 Subject: [PATCH 4/5] added convert_notebooks.py script for ipynb to pdf conversion --- README.md | 8 +++++ convert_notebooks.py | 86 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 convert_notebooks.py diff --git a/README.md b/README.md index 4e99465..4514b73 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,14 @@ To export given solution use: --output_dir output_dir ``` +## Converting notebooks to PDF files + +To export notebooks to PDF files use: + +```bash +./convert_notebooks.py --notebooks_dir ./notebooks +``` + ## Implemented Algorithms ### Matrix multiplication diff --git a/convert_notebooks.py b/convert_notebooks.py new file mode 100644 index 0000000..3e96c5d --- /dev/null +++ b/convert_notebooks.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +from pathlib import Path + +import nbformat +from nbconvert import HTMLExporter +from weasyprint import HTML + + +def ipynb_to_pdf(ipynb_path: Path, pdf_path: Path): + try: + with open(ipynb_path, encoding="utf-8") as f: + nb = nbformat.read(f, as_version=4) + + html_exporter = HTMLExporter() + html_exporter.exclude_input_prompt = True + html_exporter.exclude_output_prompt = True + body, _ = html_exporter.from_notebook_node(nb) + + HTML(string=body, base_url=str(ipynb_path.parent)).write_pdf(str(pdf_path)) + print(f"Converted: {ipynb_path.name} -> {pdf_path.name}") + except Exception as e: + print(f"Failed to convert {ipynb_path.name} to pdf: {e}") + + +def sync_notebooks_to_pdf(directory_path: str, output_path: str): + base_path = Path(directory_path) + + if output_path: + out_base = Path(output_path) + else: + out_base = base_path / "resources" + + notebooks = list(base_path.glob("*.ipynb")) + + if not notebooks: + print(f"No notebooks found in: {directory_path}") + return + + for ipynb_path in notebooks: + pdf_path = out_base / ipynb_path.with_suffix(".pdf").name + + should_convert = False + + if not pdf_path.exists(): + should_convert = True + else: + ipynb_mtime = ipynb_path.stat().st_mtime + pdf_mtime = pdf_path.stat().st_mtime + + if ipynb_mtime > pdf_mtime: + should_convert = True + + if should_convert: + ipynb_to_pdf(ipynb_path, pdf_path) + else: + print(f"Skipped: {ipynb_path.name} (PDF is up to date)") + + +def main(): + parser = argparse.ArgumentParser(description="Sync jupyter notebooks to PDF") + + parser.add_argument( + "--notebooks_dir", + "-d", + required=True, + help="Path to directory with notebooks", + ) + + parser.add_argument( + "--output_dir", + "-o", + required=False, + default=None, + help="Output directory for PDF files", + ) + + args = parser.parse_args() + + sync_notebooks_to_pdf(args.notebooks_dir, args.output_dir) + + +if __name__ == "__main__": + main() From 814d4d7510c3277ede5fb7d2fb0486442c1f8aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kowalczyk?= Date: Tue, 28 Apr 2026 12:16:42 +0200 Subject: [PATCH 5/5] updated matrix norms notebook --- notebooks/matrix_norms.ipynb | 23 ++++++++++------------- notebooks/resources/matrix_norms.pdf | Bin 5553 -> 8949 bytes 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/notebooks/matrix_norms.ipynb b/notebooks/matrix_norms.ipynb index 6cce2e6..64e8bad 100644 --- a/notebooks/matrix_norms.ipynb +++ b/notebooks/matrix_norms.ipynb @@ -5,30 +5,27 @@ "id": "a65bf3b0", "metadata": {}, "source": [ - "# Normy macierzowe" + "# Normy macierzowe\n", + "\n", + "### **Autorzy: Maja Byrecka, MichaƂ Kowalczyk**" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "72cbd32f", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "-2.0" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "-2.0\n" + ] } ], "source": [ - "import matrix_calculus.linalg as linalg\n", - "\n", - "linalg.det([[1,2],[3, 4]])" + "from matrix_calculus.matrix_norms import first_norm, second_norm, p_norm, inf_norm" ] } ], diff --git a/notebooks/resources/matrix_norms.pdf b/notebooks/resources/matrix_norms.pdf index c7bb0395a1374a0401f857317594f858232ab1dd..1187b1e9d895243f63f79c4cb4271548e7bca2a1 100644 GIT binary patch delta 8300 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 delta 4878 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 l?LU!+4~ZZBFr+<0ZDW09lr+IWjV-_jfq