-
- Read the Docs
- v: ${config.versions.current.slug}
-
-
-
-
- ${renderLanguages(config)}
- ${renderVersions(config)}
- ${renderDownloads(config)}
-
- - On Read the Docs
- -
- Project Home
-
- -
- Builds
-
- -
- Downloads
-
-
-
- - Search
- -
-
-
-
-
-
- Hosted by Read the Docs
-
-
-
- `;
-
- // Inject the generated flyout into the body HTML element.
- document.body.insertAdjacentHTML("beforeend", flyout);
-
- // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout.
- document
- .querySelector("#flyout-search-form")
- .addEventListener("focusin", () => {
- const event = new CustomEvent("readthedocs-search-show");
- document.dispatchEvent(event);
- });
- })
-}
-
-if (themeLanguageSelector || themeVersionSelector) {
- function onSelectorSwitch(event) {
- const option = event.target.selectedIndex;
- const item = event.target.options[option];
- window.location.href = item.dataset.url;
- }
-
- document.addEventListener("readthedocs-addons-data-ready", function (event) {
- const config = event.detail.data();
-
- const versionSwitch = document.querySelector(
- "div.switch-menus > div.version-switch",
- );
- if (themeVersionSelector) {
- let versions = config.versions.active;
- if (config.versions.current.hidden || config.versions.current.type === "external") {
- versions.unshift(config.versions.current);
- }
- const versionSelect = `
-
- `;
-
- versionSwitch.innerHTML = versionSelect;
- versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch);
- }
-
- const languageSwitch = document.querySelector(
- "div.switch-menus > div.language-switch",
- );
-
- if (themeLanguageSelector) {
- if (config.projects.translations.length) {
- // Add the current language to the options on the selector
- let languages = config.projects.translations.concat(
- config.projects.current,
- );
- languages = languages.sort((a, b) =>
- a.language.name.localeCompare(b.language.name),
- );
-
- const languageSelect = `
-
- `;
-
- languageSwitch.innerHTML = languageSelect;
- languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch);
- }
- else {
- languageSwitch.remove();
- }
- }
- });
-}
-
-document.addEventListener("readthedocs-addons-data-ready", function (event) {
- // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav.
- document
- .querySelector("[role='search'] input")
- .addEventListener("focusin", () => {
- const event = new CustomEvent("readthedocs-search-show");
- document.dispatchEvent(event);
- });
-});
\ No newline at end of file
diff --git a/doc/_build/html/_static/language_data.js b/doc/_build/html/_static/language_data.js
deleted file mode 100644
index c7fe6c6..0000000
--- a/doc/_build/html/_static/language_data.js
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * This script contains the language-specific data used by searchtools.js,
- * namely the list of stopwords, stemmer, scorer and splitter.
- */
-
-var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"];
-
-
-/* Non-minified version is copied as a separate JS file, if available */
-
-/**
- * Porter Stemmer
- */
-var Stemmer = function() {
-
- var step2list = {
- ational: 'ate',
- tional: 'tion',
- enci: 'ence',
- anci: 'ance',
- izer: 'ize',
- bli: 'ble',
- alli: 'al',
- entli: 'ent',
- eli: 'e',
- ousli: 'ous',
- ization: 'ize',
- ation: 'ate',
- ator: 'ate',
- alism: 'al',
- iveness: 'ive',
- fulness: 'ful',
- ousness: 'ous',
- aliti: 'al',
- iviti: 'ive',
- biliti: 'ble',
- logi: 'log'
- };
-
- var step3list = {
- icate: 'ic',
- ative: '',
- alize: 'al',
- iciti: 'ic',
- ical: 'ic',
- ful: '',
- ness: ''
- };
-
- var c = "[^aeiou]"; // consonant
- var v = "[aeiouy]"; // vowel
- var C = c + "[^aeiouy]*"; // consonant sequence
- var V = v + "[aeiou]*"; // vowel sequence
-
- var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
- var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
- var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
- var s_v = "^(" + C + ")?" + v; // vowel in stem
-
- this.stemWord = function (w) {
- var stem;
- var suffix;
- var firstch;
- var origword = w;
-
- if (w.length < 3)
- return w;
-
- var re;
- var re2;
- var re3;
- var re4;
-
- firstch = w.substr(0,1);
- if (firstch == "y")
- w = firstch.toUpperCase() + w.substr(1);
-
- // Step 1a
- re = /^(.+?)(ss|i)es$/;
- re2 = /^(.+?)([^s])s$/;
-
- if (re.test(w))
- w = w.replace(re,"$1$2");
- else if (re2.test(w))
- w = w.replace(re2,"$1$2");
-
- // Step 1b
- re = /^(.+?)eed$/;
- re2 = /^(.+?)(ed|ing)$/;
- if (re.test(w)) {
- var fp = re.exec(w);
- re = new RegExp(mgr0);
- if (re.test(fp[1])) {
- re = /.$/;
- w = w.replace(re,"");
- }
- }
- else if (re2.test(w)) {
- var fp = re2.exec(w);
- stem = fp[1];
- re2 = new RegExp(s_v);
- if (re2.test(stem)) {
- w = stem;
- re2 = /(at|bl|iz)$/;
- re3 = new RegExp("([^aeiouylsz])\\1$");
- re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
- if (re2.test(w))
- w = w + "e";
- else if (re3.test(w)) {
- re = /.$/;
- w = w.replace(re,"");
- }
- else if (re4.test(w))
- w = w + "e";
- }
- }
-
- // Step 1c
- re = /^(.+?)y$/;
- if (re.test(w)) {
- var fp = re.exec(w);
- stem = fp[1];
- re = new RegExp(s_v);
- if (re.test(stem))
- w = stem + "i";
- }
-
- // Step 2
- re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
- if (re.test(w)) {
- var fp = re.exec(w);
- stem = fp[1];
- suffix = fp[2];
- re = new RegExp(mgr0);
- if (re.test(stem))
- w = stem + step2list[suffix];
- }
-
- // Step 3
- re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
- if (re.test(w)) {
- var fp = re.exec(w);
- stem = fp[1];
- suffix = fp[2];
- re = new RegExp(mgr0);
- if (re.test(stem))
- w = stem + step3list[suffix];
- }
-
- // Step 4
- re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
- re2 = /^(.+?)(s|t)(ion)$/;
- if (re.test(w)) {
- var fp = re.exec(w);
- stem = fp[1];
- re = new RegExp(mgr1);
- if (re.test(stem))
- w = stem;
- }
- else if (re2.test(w)) {
- var fp = re2.exec(w);
- stem = fp[1] + fp[2];
- re2 = new RegExp(mgr1);
- if (re2.test(stem))
- w = stem;
- }
-
- // Step 5
- re = /^(.+?)e$/;
- if (re.test(w)) {
- var fp = re.exec(w);
- stem = fp[1];
- re = new RegExp(mgr1);
- re2 = new RegExp(meq1);
- re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
- if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
- w = stem;
- }
- re = /ll$/;
- re2 = new RegExp(mgr1);
- if (re.test(w) && re2.test(w)) {
- re = /.$/;
- w = w.replace(re,"");
- }
-
- // and turn initial Y back to y
- if (firstch == "y")
- w = firstch.toLowerCase() + w.substr(1);
- return w;
- }
-}
-
diff --git a/doc/_build/html/_static/minus.png b/doc/_build/html/_static/minus.png
deleted file mode 100644
index d96755f..0000000
Binary files a/doc/_build/html/_static/minus.png and /dev/null differ
diff --git a/doc/_build/html/_static/plus.png b/doc/_build/html/_static/plus.png
deleted file mode 100644
index 7107cec..0000000
Binary files a/doc/_build/html/_static/plus.png and /dev/null differ
diff --git a/doc/_build/html/_static/pygments.css b/doc/_build/html/_static/pygments.css
deleted file mode 100644
index 84ab303..0000000
--- a/doc/_build/html/_static/pygments.css
+++ /dev/null
@@ -1,75 +0,0 @@
-pre { line-height: 125%; }
-td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
-span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
-td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
-span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
-.highlight .hll { background-color: #ffffcc }
-.highlight { background: #f8f8f8; }
-.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */
-.highlight .err { border: 1px solid #FF0000 } /* Error */
-.highlight .k { color: #008000; font-weight: bold } /* Keyword */
-.highlight .o { color: #666666 } /* Operator */
-.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
-.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
-.highlight .cp { color: #9C6500 } /* Comment.Preproc */
-.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
-.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
-.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
-.highlight .gd { color: #A00000 } /* Generic.Deleted */
-.highlight .ge { font-style: italic } /* Generic.Emph */
-.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
-.highlight .gr { color: #E40000 } /* Generic.Error */
-.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
-.highlight .gi { color: #008400 } /* Generic.Inserted */
-.highlight .go { color: #717171 } /* Generic.Output */
-.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
-.highlight .gs { font-weight: bold } /* Generic.Strong */
-.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
-.highlight .gt { color: #0044DD } /* Generic.Traceback */
-.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
-.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
-.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
-.highlight .kp { color: #008000 } /* Keyword.Pseudo */
-.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
-.highlight .kt { color: #B00040 } /* Keyword.Type */
-.highlight .m { color: #666666 } /* Literal.Number */
-.highlight .s { color: #BA2121 } /* Literal.String */
-.highlight .na { color: #687822 } /* Name.Attribute */
-.highlight .nb { color: #008000 } /* Name.Builtin */
-.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */
-.highlight .no { color: #880000 } /* Name.Constant */
-.highlight .nd { color: #AA22FF } /* Name.Decorator */
-.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */
-.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
-.highlight .nf { color: #0000FF } /* Name.Function */
-.highlight .nl { color: #767600 } /* Name.Label */
-.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
-.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
-.highlight .nv { color: #19177C } /* Name.Variable */
-.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
-.highlight .w { color: #bbbbbb } /* Text.Whitespace */
-.highlight .mb { color: #666666 } /* Literal.Number.Bin */
-.highlight .mf { color: #666666 } /* Literal.Number.Float */
-.highlight .mh { color: #666666 } /* Literal.Number.Hex */
-.highlight .mi { color: #666666 } /* Literal.Number.Integer */
-.highlight .mo { color: #666666 } /* Literal.Number.Oct */
-.highlight .sa { color: #BA2121 } /* Literal.String.Affix */
-.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
-.highlight .sc { color: #BA2121 } /* Literal.String.Char */
-.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */
-.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
-.highlight .s2 { color: #BA2121 } /* Literal.String.Double */
-.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
-.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
-.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
-.highlight .sx { color: #008000 } /* Literal.String.Other */
-.highlight .sr { color: #A45A77 } /* Literal.String.Regex */
-.highlight .s1 { color: #BA2121 } /* Literal.String.Single */
-.highlight .ss { color: #19177C } /* Literal.String.Symbol */
-.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
-.highlight .fm { color: #0000FF } /* Name.Function.Magic */
-.highlight .vc { color: #19177C } /* Name.Variable.Class */
-.highlight .vg { color: #19177C } /* Name.Variable.Global */
-.highlight .vi { color: #19177C } /* Name.Variable.Instance */
-.highlight .vm { color: #19177C } /* Name.Variable.Magic */
-.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
\ No newline at end of file
diff --git a/doc/_build/html/_static/searchtools.js b/doc/_build/html/_static/searchtools.js
deleted file mode 100644
index 2c774d1..0000000
--- a/doc/_build/html/_static/searchtools.js
+++ /dev/null
@@ -1,632 +0,0 @@
-/*
- * Sphinx JavaScript utilities for the full-text search.
- */
-"use strict";
-
-/**
- * Simple result scoring code.
- */
-if (typeof Scorer === "undefined") {
- var Scorer = {
- // Implement the following function to further tweak the score for each result
- // The function takes a result array [docname, title, anchor, descr, score, filename]
- // and returns the new score.
- /*
- score: result => {
- const [docname, title, anchor, descr, score, filename, kind] = result
- return score
- },
- */
-
- // query matches the full name of an object
- objNameMatch: 11,
- // or matches in the last dotted part of the object name
- objPartialMatch: 6,
- // Additive scores depending on the priority of the object
- objPrio: {
- 0: 15, // used to be importantResults
- 1: 5, // used to be objectResults
- 2: -5, // used to be unimportantResults
- },
- // Used when the priority is not in the mapping.
- objPrioDefault: 0,
-
- // query found in title
- title: 15,
- partialTitle: 7,
- // query found in terms
- term: 5,
- partialTerm: 2,
- };
-}
-
-// Global search result kind enum, used by themes to style search results.
-class SearchResultKind {
- static get index() { return "index"; }
- static get object() { return "object"; }
- static get text() { return "text"; }
- static get title() { return "title"; }
-}
-
-const _removeChildren = (element) => {
- while (element && element.lastChild) element.removeChild(element.lastChild);
-};
-
-/**
- * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
- */
-const _escapeRegExp = (string) =>
- string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
-
-const _displayItem = (item, searchTerms, highlightTerms) => {
- const docBuilder = DOCUMENTATION_OPTIONS.BUILDER;
- const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX;
- const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX;
- const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY;
- const contentRoot = document.documentElement.dataset.content_root;
-
- const [docName, title, anchor, descr, score, _filename, kind] = item;
-
- let listItem = document.createElement("li");
- // Add a class representing the item's type:
- // can be used by a theme's CSS selector for styling
- // See SearchResultKind for the class names.
- listItem.classList.add(`kind-${kind}`);
- let requestUrl;
- let linkUrl;
- if (docBuilder === "dirhtml") {
- // dirhtml builder
- let dirname = docName + "/";
- if (dirname.match(/\/index\/$/))
- dirname = dirname.substring(0, dirname.length - 6);
- else if (dirname === "index/") dirname = "";
- requestUrl = contentRoot + dirname;
- linkUrl = requestUrl;
- } else {
- // normal html builders
- requestUrl = contentRoot + docName + docFileSuffix;
- linkUrl = docName + docLinkSuffix;
- }
- let linkEl = listItem.appendChild(document.createElement("a"));
- linkEl.href = linkUrl + anchor;
- linkEl.dataset.score = score;
- linkEl.innerHTML = title;
- if (descr) {
- listItem.appendChild(document.createElement("span")).innerHTML =
- " (" + descr + ")";
- // highlight search terms in the description
- if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
- highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
- }
- else if (showSearchSummary)
- fetch(requestUrl)
- .then((responseData) => responseData.text())
- .then((data) => {
- if (data)
- listItem.appendChild(
- Search.makeSearchSummary(data, searchTerms, anchor)
- );
- // highlight search terms in the summary
- if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
- highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
- });
- Search.output.appendChild(listItem);
-};
-const _finishSearch = (resultCount) => {
- Search.stopPulse();
- Search.title.innerText = _("Search Results");
- if (!resultCount)
- Search.status.innerText = Documentation.gettext(
- "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories."
- );
- else
- Search.status.innerText = Documentation.ngettext(
- "Search finished, found one page matching the search query.",
- "Search finished, found ${resultCount} pages matching the search query.",
- resultCount,
- ).replace('${resultCount}', resultCount);
-};
-const _displayNextItem = (
- results,
- resultCount,
- searchTerms,
- highlightTerms,
-) => {
- // results left, load the summary and display it
- // this is intended to be dynamic (don't sub resultsCount)
- if (results.length) {
- _displayItem(results.pop(), searchTerms, highlightTerms);
- setTimeout(
- () => _displayNextItem(results, resultCount, searchTerms, highlightTerms),
- 5
- );
- }
- // search finished, update title and status message
- else _finishSearch(resultCount);
-};
-// Helper function used by query() to order search results.
-// Each input is an array of [docname, title, anchor, descr, score, filename, kind].
-// Order the results by score (in opposite order of appearance, since the
-// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically.
-const _orderResultsByScoreThenName = (a, b) => {
- const leftScore = a[4];
- const rightScore = b[4];
- if (leftScore === rightScore) {
- // same score: sort alphabetically
- const leftTitle = a[1].toLowerCase();
- const rightTitle = b[1].toLowerCase();
- if (leftTitle === rightTitle) return 0;
- return leftTitle > rightTitle ? -1 : 1; // inverted is intentional
- }
- return leftScore > rightScore ? 1 : -1;
-};
-
-/**
- * Default splitQuery function. Can be overridden in ``sphinx.search`` with a
- * custom function per language.
- *
- * The regular expression works by splitting the string on consecutive characters
- * that are not Unicode letters, numbers, underscores, or emoji characters.
- * This is the same as ``\W+`` in Python, preserving the surrogate pair area.
- */
-if (typeof splitQuery === "undefined") {
- var splitQuery = (query) => query
- .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu)
- .filter(term => term) // remove remaining empty strings
-}
-
-/**
- * Search Module
- */
-const Search = {
- _index: null,
- _queued_query: null,
- _pulse_status: -1,
-
- htmlToText: (htmlString, anchor) => {
- const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html');
- for (const removalQuery of [".headerlink", "script", "style"]) {
- htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() });
- }
- if (anchor) {
- const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`);
- if (anchorContent) return anchorContent.textContent;
-
- console.warn(
- `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`
- );
- }
-
- // if anchor not specified or not found, fall back to main content
- const docContent = htmlElement.querySelector('[role="main"]');
- if (docContent) return docContent.textContent;
-
- console.warn(
- "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template."
- );
- return "";
- },
-
- init: () => {
- const query = new URLSearchParams(window.location.search).get("q");
- document
- .querySelectorAll('input[name="q"]')
- .forEach((el) => (el.value = query));
- if (query) Search.performSearch(query);
- },
-
- loadIndex: (url) =>
- (document.body.appendChild(document.createElement("script")).src = url),
-
- setIndex: (index) => {
- Search._index = index;
- if (Search._queued_query !== null) {
- const query = Search._queued_query;
- Search._queued_query = null;
- Search.query(query);
- }
- },
-
- hasIndex: () => Search._index !== null,
-
- deferQuery: (query) => (Search._queued_query = query),
-
- stopPulse: () => (Search._pulse_status = -1),
-
- startPulse: () => {
- if (Search._pulse_status >= 0) return;
-
- const pulse = () => {
- Search._pulse_status = (Search._pulse_status + 1) % 4;
- Search.dots.innerText = ".".repeat(Search._pulse_status);
- if (Search._pulse_status >= 0) window.setTimeout(pulse, 500);
- };
- pulse();
- },
-
- /**
- * perform a search for something (or wait until index is loaded)
- */
- performSearch: (query) => {
- // create the required interface elements
- const searchText = document.createElement("h2");
- searchText.textContent = _("Searching");
- const searchSummary = document.createElement("p");
- searchSummary.classList.add("search-summary");
- searchSummary.innerText = "";
- const searchList = document.createElement("ul");
- searchList.setAttribute("role", "list");
- searchList.classList.add("search");
-
- const out = document.getElementById("search-results");
- Search.title = out.appendChild(searchText);
- Search.dots = Search.title.appendChild(document.createElement("span"));
- Search.status = out.appendChild(searchSummary);
- Search.output = out.appendChild(searchList);
-
- const searchProgress = document.getElementById("search-progress");
- // Some themes don't use the search progress node
- if (searchProgress) {
- searchProgress.innerText = _("Preparing search...");
- }
- Search.startPulse();
-
- // index already loaded, the browser was quick!
- if (Search.hasIndex()) Search.query(query);
- else Search.deferQuery(query);
- },
-
- _parseQuery: (query) => {
- // stem the search terms and add them to the correct list
- const stemmer = new Stemmer();
- const searchTerms = new Set();
- const excludedTerms = new Set();
- const highlightTerms = new Set();
- const objectTerms = new Set(splitQuery(query.toLowerCase().trim()));
- splitQuery(query.trim()).forEach((queryTerm) => {
- const queryTermLower = queryTerm.toLowerCase();
-
- // maybe skip this "word"
- // stopwords array is from language_data.js
- if (
- stopwords.indexOf(queryTermLower) !== -1 ||
- queryTerm.match(/^\d+$/)
- )
- return;
-
- // stem the word
- let word = stemmer.stemWord(queryTermLower);
- // select the correct list
- if (word[0] === "-") excludedTerms.add(word.substr(1));
- else {
- searchTerms.add(word);
- highlightTerms.add(queryTermLower);
- }
- });
-
- if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js
- localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" "))
- }
-
- // console.debug("SEARCH: searching for:");
- // console.info("required: ", [...searchTerms]);
- // console.info("excluded: ", [...excludedTerms]);
-
- return [query, searchTerms, excludedTerms, highlightTerms, objectTerms];
- },
-
- /**
- * execute search (requires search index to be loaded)
- */
- _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => {
- const filenames = Search._index.filenames;
- const docNames = Search._index.docnames;
- const titles = Search._index.titles;
- const allTitles = Search._index.alltitles;
- const indexEntries = Search._index.indexentries;
-
- // Collect multiple result groups to be sorted separately and then ordered.
- // Each is an array of [docname, title, anchor, descr, score, filename, kind].
- const normalResults = [];
- const nonMainIndexResults = [];
-
- _removeChildren(document.getElementById("search-progress"));
-
- const queryLower = query.toLowerCase().trim();
- for (const [title, foundTitles] of Object.entries(allTitles)) {
- if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) {
- for (const [file, id] of foundTitles) {
- const score = Math.round(Scorer.title * queryLower.length / title.length);
- const boost = titles[file] === title ? 1 : 0; // add a boost for document titles
- normalResults.push([
- docNames[file],
- titles[file] !== title ? `${titles[file]} > ${title}` : title,
- id !== null ? "#" + id : "",
- null,
- score + boost,
- filenames[file],
- SearchResultKind.title,
- ]);
- }
- }
- }
-
- // search for explicit entries in index directives
- for (const [entry, foundEntries] of Object.entries(indexEntries)) {
- if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) {
- for (const [file, id, isMain] of foundEntries) {
- const score = Math.round(100 * queryLower.length / entry.length);
- const result = [
- docNames[file],
- titles[file],
- id ? "#" + id : "",
- null,
- score,
- filenames[file],
- SearchResultKind.index,
- ];
- if (isMain) {
- normalResults.push(result);
- } else {
- nonMainIndexResults.push(result);
- }
- }
- }
- }
-
- // lookup as object
- objectTerms.forEach((term) =>
- normalResults.push(...Search.performObjectSearch(term, objectTerms))
- );
-
- // lookup as search terms in fulltext
- normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms));
-
- // let the scorer override scores with a custom scoring function
- if (Scorer.score) {
- normalResults.forEach((item) => (item[4] = Scorer.score(item)));
- nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item)));
- }
-
- // Sort each group of results by score and then alphabetically by name.
- normalResults.sort(_orderResultsByScoreThenName);
- nonMainIndexResults.sort(_orderResultsByScoreThenName);
-
- // Combine the result groups in (reverse) order.
- // Non-main index entries are typically arbitrary cross-references,
- // so display them after other results.
- let results = [...nonMainIndexResults, ...normalResults];
-
- // remove duplicate search results
- // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept
- let seen = new Set();
- results = results.reverse().reduce((acc, result) => {
- let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(',');
- if (!seen.has(resultStr)) {
- acc.push(result);
- seen.add(resultStr);
- }
- return acc;
- }, []);
-
- return results.reverse();
- },
-
- query: (query) => {
- const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query);
- const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms);
-
- // for debugging
- //Search.lastresults = results.slice(); // a copy
- // console.info("search results:", Search.lastresults);
-
- // print the results
- _displayNextItem(results, results.length, searchTerms, highlightTerms);
- },
-
- /**
- * search for object names
- */
- performObjectSearch: (object, objectTerms) => {
- const filenames = Search._index.filenames;
- const docNames = Search._index.docnames;
- const objects = Search._index.objects;
- const objNames = Search._index.objnames;
- const titles = Search._index.titles;
-
- const results = [];
-
- const objectSearchCallback = (prefix, match) => {
- const name = match[4]
- const fullname = (prefix ? prefix + "." : "") + name;
- const fullnameLower = fullname.toLowerCase();
- if (fullnameLower.indexOf(object) < 0) return;
-
- let score = 0;
- const parts = fullnameLower.split(".");
-
- // check for different match types: exact matches of full name or
- // "last name" (i.e. last dotted part)
- if (fullnameLower === object || parts.slice(-1)[0] === object)
- score += Scorer.objNameMatch;
- else if (parts.slice(-1)[0].indexOf(object) > -1)
- score += Scorer.objPartialMatch; // matches in last name
-
- const objName = objNames[match[1]][2];
- const title = titles[match[0]];
-
- // If more than one term searched for, we require other words to be
- // found in the name/title/description
- const otherTerms = new Set(objectTerms);
- otherTerms.delete(object);
- if (otherTerms.size > 0) {
- const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase();
- if (
- [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0)
- )
- return;
- }
-
- let anchor = match[3];
- if (anchor === "") anchor = fullname;
- else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname;
-
- const descr = objName + _(", in ") + title;
-
- // add custom score for some objects according to scorer
- if (Scorer.objPrio.hasOwnProperty(match[2]))
- score += Scorer.objPrio[match[2]];
- else score += Scorer.objPrioDefault;
-
- results.push([
- docNames[match[0]],
- fullname,
- "#" + anchor,
- descr,
- score,
- filenames[match[0]],
- SearchResultKind.object,
- ]);
- };
- Object.keys(objects).forEach((prefix) =>
- objects[prefix].forEach((array) =>
- objectSearchCallback(prefix, array)
- )
- );
- return results;
- },
-
- /**
- * search for full-text terms in the index
- */
- performTermsSearch: (searchTerms, excludedTerms) => {
- // prepare search
- const terms = Search._index.terms;
- const titleTerms = Search._index.titleterms;
- const filenames = Search._index.filenames;
- const docNames = Search._index.docnames;
- const titles = Search._index.titles;
-
- const scoreMap = new Map();
- const fileMap = new Map();
-
- // perform the search on the required terms
- searchTerms.forEach((word) => {
- const files = [];
- const arr = [
- { files: terms[word], score: Scorer.term },
- { files: titleTerms[word], score: Scorer.title },
- ];
- // add support for partial matches
- if (word.length > 2) {
- const escapedWord = _escapeRegExp(word);
- if (!terms.hasOwnProperty(word)) {
- Object.keys(terms).forEach((term) => {
- if (term.match(escapedWord))
- arr.push({ files: terms[term], score: Scorer.partialTerm });
- });
- }
- if (!titleTerms.hasOwnProperty(word)) {
- Object.keys(titleTerms).forEach((term) => {
- if (term.match(escapedWord))
- arr.push({ files: titleTerms[term], score: Scorer.partialTitle });
- });
- }
- }
-
- // no match but word was a required one
- if (arr.every((record) => record.files === undefined)) return;
-
- // found search word in contents
- arr.forEach((record) => {
- if (record.files === undefined) return;
-
- let recordFiles = record.files;
- if (recordFiles.length === undefined) recordFiles = [recordFiles];
- files.push(...recordFiles);
-
- // set score for the word in each file
- recordFiles.forEach((file) => {
- if (!scoreMap.has(file)) scoreMap.set(file, {});
- scoreMap.get(file)[word] = record.score;
- });
- });
-
- // create the mapping
- files.forEach((file) => {
- if (!fileMap.has(file)) fileMap.set(file, [word]);
- else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word);
- });
- });
-
- // now check if the files don't contain excluded terms
- const results = [];
- for (const [file, wordList] of fileMap) {
- // check if all requirements are matched
-
- // as search terms with length < 3 are discarded
- const filteredTermCount = [...searchTerms].filter(
- (term) => term.length > 2
- ).length;
- if (
- wordList.length !== searchTerms.size &&
- wordList.length !== filteredTermCount
- )
- continue;
-
- // ensure that none of the excluded terms is in the search result
- if (
- [...excludedTerms].some(
- (term) =>
- terms[term] === file ||
- titleTerms[term] === file ||
- (terms[term] || []).includes(file) ||
- (titleTerms[term] || []).includes(file)
- )
- )
- break;
-
- // select one (max) score for the file.
- const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w]));
- // add result to the result list
- results.push([
- docNames[file],
- titles[file],
- "",
- null,
- score,
- filenames[file],
- SearchResultKind.text,
- ]);
- }
- return results;
- },
-
- /**
- * helper function to return a node containing the
- * search summary for a given text. keywords is a list
- * of stemmed words.
- */
- makeSearchSummary: (htmlText, keywords, anchor) => {
- const text = Search.htmlToText(htmlText, anchor);
- if (text === "") return null;
-
- const textLower = text.toLowerCase();
- const actualStartPosition = [...keywords]
- .map((k) => textLower.indexOf(k.toLowerCase()))
- .filter((i) => i > -1)
- .slice(-1)[0];
- const startWithContext = Math.max(actualStartPosition - 120, 0);
-
- const top = startWithContext === 0 ? "" : "...";
- const tail = startWithContext + 240 < text.length ? "..." : "";
-
- let summary = document.createElement("p");
- summary.classList.add("context");
- summary.textContent = top + text.substr(startWithContext, 240).trim() + tail;
-
- return summary;
- },
-};
-
-_ready(Search.init);
diff --git a/doc/_build/html/_static/sphinx_highlight.js b/doc/_build/html/_static/sphinx_highlight.js
deleted file mode 100644
index 8a96c69..0000000
--- a/doc/_build/html/_static/sphinx_highlight.js
+++ /dev/null
@@ -1,154 +0,0 @@
-/* Highlighting utilities for Sphinx HTML documentation. */
-"use strict";
-
-const SPHINX_HIGHLIGHT_ENABLED = true
-
-/**
- * highlight a given string on a node by wrapping it in
- * span elements with the given class name.
- */
-const _highlight = (node, addItems, text, className) => {
- if (node.nodeType === Node.TEXT_NODE) {
- const val = node.nodeValue;
- const parent = node.parentNode;
- const pos = val.toLowerCase().indexOf(text);
- if (
- pos >= 0 &&
- !parent.classList.contains(className) &&
- !parent.classList.contains("nohighlight")
- ) {
- let span;
-
- const closestNode = parent.closest("body, svg, foreignObject");
- const isInSVG = closestNode && closestNode.matches("svg");
- if (isInSVG) {
- span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
- } else {
- span = document.createElement("span");
- span.classList.add(className);
- }
-
- span.appendChild(document.createTextNode(val.substr(pos, text.length)));
- const rest = document.createTextNode(val.substr(pos + text.length));
- parent.insertBefore(
- span,
- parent.insertBefore(
- rest,
- node.nextSibling
- )
- );
- node.nodeValue = val.substr(0, pos);
- /* There may be more occurrences of search term in this node. So call this
- * function recursively on the remaining fragment.
- */
- _highlight(rest, addItems, text, className);
-
- if (isInSVG) {
- const rect = document.createElementNS(
- "http://www.w3.org/2000/svg",
- "rect"
- );
- const bbox = parent.getBBox();
- rect.x.baseVal.value = bbox.x;
- rect.y.baseVal.value = bbox.y;
- rect.width.baseVal.value = bbox.width;
- rect.height.baseVal.value = bbox.height;
- rect.setAttribute("class", className);
- addItems.push({ parent: parent, target: rect });
- }
- }
- } else if (node.matches && !node.matches("button, select, textarea")) {
- node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
- }
-};
-const _highlightText = (thisNode, text, className) => {
- let addItems = [];
- _highlight(thisNode, addItems, text, className);
- addItems.forEach((obj) =>
- obj.parent.insertAdjacentElement("beforebegin", obj.target)
- );
-};
-
-/**
- * Small JavaScript module for the documentation.
- */
-const SphinxHighlight = {
-
- /**
- * highlight the search words provided in localstorage in the text
- */
- highlightSearchWords: () => {
- if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
-
- // get and clear terms from localstorage
- const url = new URL(window.location);
- const highlight =
- localStorage.getItem("sphinx_highlight_terms")
- || url.searchParams.get("highlight")
- || "";
- localStorage.removeItem("sphinx_highlight_terms")
- url.searchParams.delete("highlight");
- window.history.replaceState({}, "", url);
-
- // get individual terms from highlight string
- const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
- if (terms.length === 0) return; // nothing to do
-
- // There should never be more than one element matching "div.body"
- const divBody = document.querySelectorAll("div.body");
- const body = divBody.length ? divBody[0] : document.querySelector("body");
- window.setTimeout(() => {
- terms.forEach((term) => _highlightText(body, term, "highlighted"));
- }, 10);
-
- const searchBox = document.getElementById("searchbox");
- if (searchBox === null) return;
- searchBox.appendChild(
- document
- .createRange()
- .createContextualFragment(
- '
' +
- '' +
- _("Hide Search Matches") +
- "
"
- )
- );
- },
-
- /**
- * helper function to hide the search marks again
- */
- hideSearchWords: () => {
- document
- .querySelectorAll("#searchbox .highlight-link")
- .forEach((el) => el.remove());
- document
- .querySelectorAll("span.highlighted")
- .forEach((el) => el.classList.remove("highlighted"));
- localStorage.removeItem("sphinx_highlight_terms")
- },
-
- initEscapeListener: () => {
- // only install a listener if it is really needed
- if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
-
- document.addEventListener("keydown", (event) => {
- // bail for input elements
- if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
- // bail with special keys
- if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
- if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
- SphinxHighlight.hideSearchWords();
- event.preventDefault();
- }
- });
- },
-};
-
-_ready(() => {
- /* Do not call highlightSearchWords() when we are on the search page.
- * It will highlight words from the *previous* search query.
- */
- if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords();
- SphinxHighlight.initEscapeListener();
-});
diff --git a/doc/_build/html/genindex.html b/doc/_build/html/genindex.html
deleted file mode 100644
index 858fe71..0000000
--- a/doc/_build/html/genindex.html
+++ /dev/null
@@ -1,103 +0,0 @@
-
-
-
-
-
-
-
-
Index — pyTorchAutoForge 0.1 documentation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/doc/_build/html/index.html b/doc/_build/html/index.html
deleted file mode 100644
index 409eb8f..0000000
--- a/doc/_build/html/index.html
+++ /dev/null
@@ -1,116 +0,0 @@
-
-
-
-
-
-
-
-
-
Welcome to pyTorchAutoForge’s documentation! — pyTorchAutoForge 0.1 documentation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Welcome to pyTorchAutoForge’s documentation!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/doc/_build/html/objects.inv b/doc/_build/html/objects.inv
deleted file mode 100644
index 5ecc6fa..0000000
Binary files a/doc/_build/html/objects.inv and /dev/null differ
diff --git a/doc/_build/html/py-modindex.html b/doc/_build/html/py-modindex.html
deleted file mode 100644
index 51617da..0000000
--- a/doc/_build/html/py-modindex.html
+++ /dev/null
@@ -1,155 +0,0 @@
-
-
-
-
-
-
Python Module Index — pyTorchAutoForge 0.1 documentation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - Python Module Index
- -
-
-
-
-
-
-
-
-
-
Python Module Index
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/doc/_build/html/search.html b/doc/_build/html/search.html
deleted file mode 100644
index a44eeb4..0000000
--- a/doc/_build/html/search.html
+++ /dev/null
@@ -1,118 +0,0 @@
-
-
-
-
-
-
-
-
Search — pyTorchAutoForge 0.1 documentation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/doc/_build/html/searchindex.js b/doc/_build/html/searchindex.js
deleted file mode 100644
index dc9dad2..0000000
--- a/doc/_build/html/searchindex.js
+++ /dev/null
@@ -1 +0,0 @@
-Search.setIndex({"alltitles": {"Indices and tables": [[0, "indices-and-tables"]], "Welcome to pyTorchAutoForge\u2019s documentation!": [[0, null]]}, "docnames": ["index"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["index.rst"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"index": 0, "modul": 0, "page": 0, "search": 0}, "titles": ["Welcome to pyTorchAutoForge\u2019s documentation!"], "titleterms": {"": 0, "document": 0, "indic": 0, "pytorchautoforg": 0, "tabl": 0, "welcom": 0}})
\ No newline at end of file
diff --git a/doc/_static/ptaf_doc_theme.css b/doc/_static/ptaf_doc_theme.css
deleted file mode 100644
index dc0af92..0000000
--- a/doc/_static/ptaf_doc_theme.css
+++ /dev/null
@@ -1,59 +0,0 @@
-/* Increase the width of the content area */
-.wy-nav-content {
- max-width: 100%;
- /* Set to your preferred width */
-}
-
-.wy-side-nav-search {
- width: 100%;
- overflow: visible;
- /* Fixes the cutoff issue */
-}
-
-/* Optionally adjust the width of the side navigation */
-/*
-.wy-nav-side {
- width: 250px;
- Set a custom width for the side index
-}
-*/
-
-/* Adjust the input field of the search bar */
-.wy-side-nav-search input {
- width: 90%;
- /* Adjust the width if necessary */
-}
-
-/* Apply a dark background with light text */
-body {
- background-color: #121212;
- color: #e0e0e0;
-}
-
-/* Change text color */
-p {
- color: #256103;
-}
-
-/* Customize sidebar background and link colors */
-.wy-nav-side {
- background-color: #024463;
-}
-
-.wy-nav-side a {
- color: #026694;
-}
-
-/* Modify navigation bar */
-.wy-nav-top {
- background-color: #d46000;
-}
-
-/* Customize main content links */
-a {
- color: #72049e;
-}
-
-a:hover {
- color: #3700b3;
-}
\ No newline at end of file
diff --git a/doc/api/index.md b/doc/api/index.md
new file mode 100644
index 0000000..cc73007
--- /dev/null
+++ b/doc/api/index.md
@@ -0,0 +1,35 @@
+# API Reference
+
+Public API pages are generated from source with `sphinx-autoapi`.
+Google-style docstrings are parsed through `sphinx.ext.napoleon`.
+
+## Main Entry Points
+
+- {mod}`pyTorchAutoForge.model_building`: neural-network blocks, backbones, model assembly, and model mutation utilities.
+- {mod}`pyTorchAutoForge.datasets`: dataset containers, labels, image augmentations, and vector error models.
+- {mod}`pyTorchAutoForge.optimization`: training-manager utilities and loss functions.
+- {mod}`pyTorchAutoForge.evaluation`: model evaluation, plotting, profiling, and explainability entry points.
+- {mod}`pyTorchAutoForge.evaluation.explainability`: typed SHAP/Captum explainer subsystem.
+- {mod}`pyTorchAutoForge.api`: ONNX, TensorRT, Torch, MATLAB, TCP, runtime, and MLflow integration surfaces.
+- {mod}`pyTorchAutoForge.monitoring`: run logging helpers.
+- {mod}`pyTorchAutoForge.utils`: device, conversion, timing, and general utility helpers.
+
+## Explainability Shortcuts
+
+- {class}`pyTorchAutoForge.evaluation.explainability.ModelExplainer`
+- {class}`pyTorchAutoForge.evaluation.explainability.ExplainerConfig`
+- {class}`pyTorchAutoForge.evaluation.explainability.ExplainerRequest`
+- {class}`pyTorchAutoForge.evaluation.explainability.ShapExplainerConfig`
+- {class}`pyTorchAutoForge.evaluation.explainability.CaptumIntegratedGradientsConfig`
+- {class}`pyTorchAutoForge.evaluation.explainability.ExplainerResult`
+
+See [](../model_explainer.md) for runnable usage.
+
+## Generated API Tree
+
+```{toctree}
+:maxdepth: 2
+
+module_index
+generated/pyTorchAutoForge/index
+```
diff --git a/doc/api/module_index.md b/doc/api/module_index.md
new file mode 100644
index 0000000..a5abb20
--- /dev/null
+++ b/doc/api/module_index.md
@@ -0,0 +1,115 @@
+# Module Index
+
+This page links generated module pages in the current public documentation
+surface. AutoAPI still owns the generated pages; this page is only a curated
+index for scanning.
+
+## Package Root
+
+- {mod}`pyTorchAutoForge`
+
+## Runtime And External APIs
+
+- {mod}`pyTorchAutoForge.api`
+- {mod}`pyTorchAutoForge.api.matlab`
+- {mod}`pyTorchAutoForge.api.matlab.TorchModelMATLABwrapper`
+- {mod}`pyTorchAutoForge.api.mlflow`
+- {mod}`pyTorchAutoForge.api.mlflow.mlflow_api`
+- {mod}`pyTorchAutoForge.api.onnx`
+- {mod}`pyTorchAutoForge.api.onnx.ModelHandlerONNx`
+- {mod}`pyTorchAutoForge.api.onnx.OnnxRuntimeApi`
+- {mod}`pyTorchAutoForge.api.runtime`
+- {mod}`pyTorchAutoForge.api.runtime.ModelRuntimeApi`
+- {mod}`pyTorchAutoForge.api.tcp`
+- {mod}`pyTorchAutoForge.api.tcp.tcpServerPy`
+- {mod}`pyTorchAutoForge.api.tcp.tcp_socket_server`
+- {mod}`pyTorchAutoForge.api.tcp.tcp_torchModel_eval`
+- {mod}`pyTorchAutoForge.api.tensorrt`
+- {mod}`pyTorchAutoForge.api.tensorrt.TRTengineExporter`
+- {mod}`pyTorchAutoForge.api.tensorrt.TensorrtRuntimeApi`
+- {mod}`pyTorchAutoForge.api.torch`
+- {mod}`pyTorchAutoForge.api.torch.torchModulesIO`
+
+## Datasets
+
+- {mod}`pyTorchAutoForge.datasets`
+- {mod}`pyTorchAutoForge.datasets.AugmentationsBaseClasses`
+- {mod}`pyTorchAutoForge.datasets.AugmentationsManager`
+- {mod}`pyTorchAutoForge.datasets.DataloaderIndex`
+- {mod}`pyTorchAutoForge.datasets.DatasetClasses`
+- {mod}`pyTorchAutoForge.datasets.ImagesAugmentation`
+- {mod}`pyTorchAutoForge.datasets.LabelsClasses`
+- {mod}`pyTorchAutoForge.datasets.auxiliary_functions`
+- {mod}`pyTorchAutoForge.datasets.noise_models`
+- {mod}`pyTorchAutoForge.datasets.noise_models.FrameCameraErrorModels`
+- {mod}`pyTorchAutoForge.datasets.noise_models.GeometricAugs`
+- {mod}`pyTorchAutoForge.datasets.noise_models.IntensityAugs`
+- {mod}`pyTorchAutoForge.datasets.noise_models.NoiseErrorsAugs`
+- {mod}`pyTorchAutoForge.datasets.noise_models.OpticsErrorsAugs`
+- {mod}`pyTorchAutoForge.datasets.vector_error_models`
+- {mod}`pyTorchAutoForge.datasets.vector_error_models.VectorErrorsBaseClasses`
+
+## Evaluation And Explainability
+
+- {mod}`pyTorchAutoForge.evaluation`
+- {mod}`pyTorchAutoForge.evaluation.ModelEvaluator`
+- {mod}`pyTorchAutoForge.evaluation.ModelExplainer`
+- {mod}`pyTorchAutoForge.evaluation.ModelProfiler`
+- {mod}`pyTorchAutoForge.evaluation.ResultsPlotter`
+- {mod}`pyTorchAutoForge.evaluation.explainability`
+- {mod}`pyTorchAutoForge.evaluation.explainability.adapters`
+- {mod}`pyTorchAutoForge.evaluation.explainability.adapters.captum_adapter`
+- {mod}`pyTorchAutoForge.evaluation.explainability.adapters.shap_adapter`
+- {mod}`pyTorchAutoForge.evaluation.explainability.configs`
+- {mod}`pyTorchAutoForge.evaluation.explainability.explainer`
+- {mod}`pyTorchAutoForge.evaluation.explainability.method_configs`
+- {mod}`pyTorchAutoForge.evaluation.explainability.predictors`
+- {mod}`pyTorchAutoForge.evaluation.explainability.registry`
+- {mod}`pyTorchAutoForge.evaluation.explainability.results`
+- {mod}`pyTorchAutoForge.evaluation.explainability.schemas`
+- {mod}`pyTorchAutoForge.evaluation.explainability.targets`
+
+## Model Building
+
+- {mod}`pyTorchAutoForge.model_building`
+- {mod}`pyTorchAutoForge.model_building.ModelAssembler`
+- {mod}`pyTorchAutoForge.model_building.ModelAutoBuilder`
+- {mod}`pyTorchAutoForge.model_building.ModelMutator`
+- {mod}`pyTorchAutoForge.model_building.backbones`
+- {mod}`pyTorchAutoForge.model_building.backbones.base_backbones`
+- {mod}`pyTorchAutoForge.model_building.backbones.efficient_net`
+- {mod}`pyTorchAutoForge.model_building.backbones.image_processing_operators`
+- {mod}`pyTorchAutoForge.model_building.backbones.input_adapters`
+- {mod}`pyTorchAutoForge.model_building.backbones.spatial_features_operators`
+- {mod}`pyTorchAutoForge.model_building.convolutionalBlocks`
+- {mod}`pyTorchAutoForge.model_building.fullyConnectedBlocks`
+- {mod}`pyTorchAutoForge.model_building.modelBuildingBlocks`
+- {mod}`pyTorchAutoForge.model_building.poolingBlocks`
+
+## Optimization And Experiments
+
+- {mod}`pyTorchAutoForge.optimization`
+- {mod}`pyTorchAutoForge.optimization.ModelTrainingManager`
+- {mod}`pyTorchAutoForge.optimization.lossFunctionsClasses`
+- {mod}`pyTorchAutoForge.hparams_optim`
+- {mod}`pyTorchAutoForge.hparams_optim.ModelHparamsOptimizer`
+- {mod}`pyTorchAutoForge.hparams_optim.OptunaStudyAnalyzer`
+- {mod}`pyTorchAutoForge.hparams_optim.optuna_auxiliary`
+- {mod}`pyTorchAutoForge.hparams_optim.seedNewOptunaStudy`
+- {mod}`pyTorchAutoForge.monitoring`
+- {mod}`pyTorchAutoForge.monitoring.run_loggers`
+
+## Setup And Utilities
+
+- {mod}`pyTorchAutoForge.setup`
+- {mod}`pyTorchAutoForge.setup.AutoForgeInit`
+- {mod}`pyTorchAutoForge.setup.BaseConfigClass`
+- {mod}`pyTorchAutoForge.utils`
+- {mod}`pyTorchAutoForge.utils.DeviceManager`
+- {mod}`pyTorchAutoForge.utils.LossLandscapeVisualizer`
+- {mod}`pyTorchAutoForge.utils.argument_parsers`
+- {mod}`pyTorchAutoForge.utils.context_management`
+- {mod}`pyTorchAutoForge.utils.conversion_utils`
+- {mod}`pyTorchAutoForge.utils.rename_images`
+- {mod}`pyTorchAutoForge.utils.timing_utils`
+- {mod}`pyTorchAutoForge.utils.utils`
diff --git a/doc/_static/ptaf_logo_small.jpg b/doc/assets/ptaf_logo_small.jpg
similarity index 100%
rename from doc/_static/ptaf_logo_small.jpg
rename to doc/assets/ptaf_logo_small.jpg
diff --git a/doc/build_versioned_docs.sh b/doc/build_versioned_docs.sh
new file mode 100755
index 0000000..aa717f2
--- /dev/null
+++ b/doc/build_versioned_docs.sh
@@ -0,0 +1,124 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
+OUTPUT_DIR="${1:-site}"
+BASE_URL="${PTAF_DOC_BASE_URL:-https://petercalifano.github.io/pyTorchAutoForge}"
+SWITCHER_JSON_URL="${PTAF_DOC_SWITCHER_JSON_URL:-${BASE_URL}/_static/switcher.json}"
+
+cd "${REPO_ROOT}"
+
+rm -rf "${OUTPUT_DIR}"
+mkdir -p "${OUTPUT_DIR}/_static"
+
+build_docs_() {
+ local source_root_="$1"
+ local output_name_="$2"
+ local version_name_="$3"
+
+ if [ ! -f "${source_root_}/doc/conf.py" ]; then
+ echo "Skipping ${version_name_}: doc/conf.py not found"
+ return 0
+ fi
+
+ if [ "${version_name_}" != "stable" ] && [ ! -f "${source_root_}/doc/_static/switcher.json" ]; then
+ echo "Skipping ${version_name_}: versioned docs config not found"
+ return 0
+ fi
+
+ echo "Building docs ${version_name_} -> ${OUTPUT_DIR}/${output_name_}"
+ if ! PTAF_DOC_BASE_URL="${BASE_URL}/${output_name_}/" \
+ PTAF_DOC_VERSION="${version_name_}" \
+ PTAF_DOC_SWITCHER_JSON_URL="${SWITCHER_JSON_URL}" \
+ python -m sphinx -b html "${source_root_}/doc" "${OUTPUT_DIR}/${output_name_}"; then
+ echo "Skipping ${version_name_}: Sphinx build failed"
+ rm -rf "${OUTPUT_DIR:?}/${output_name_}"
+ fi
+}
+
+build_docs_ "${REPO_ROOT}" "stable" "stable"
+
+TEMP_WORKTREE_ROOT="$(mktemp -d)"
+cleanup_() {
+ find "${TEMP_WORKTREE_ROOT}" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null |
+ while IFS= read -r -d '' worktree_dir_; do
+ git worktree remove --force "${worktree_dir_}" >/dev/null 2>&1 || true
+ done
+ rm -rf "${TEMP_WORKTREE_ROOT}"
+}
+trap cleanup_ EXIT
+
+while IFS= read -r tag_name_; do
+ [ -n "${tag_name_}" ] || continue
+
+ output_name_="${tag_name_//\//-}"
+ worktree_dir_="${TEMP_WORKTREE_ROOT}/${output_name_}"
+
+ git worktree add --detach --quiet "${worktree_dir_}" "${tag_name_}"
+ build_docs_ "${worktree_dir_}" "${output_name_}" "${tag_name_}"
+ git worktree remove --force "${worktree_dir_}" >/dev/null
+done < <(git tag --list 'v*' --sort=-v:refname)
+
+python - "${OUTPUT_DIR}" "${BASE_URL}" <<'PY'
+from __future__ import annotations
+
+import json
+from pathlib import Path
+import sys
+
+output_dir_ = Path(sys.argv[1])
+base_url_ = sys.argv[2].rstrip("/")
+
+entries_: list[dict[str, str]] = []
+
+stable_dir_ = output_dir_ / "stable"
+if (stable_dir_ / "index.html").exists():
+ entries_.append(
+ {
+ "name": "stable",
+ "version": "stable",
+ "url": f"{base_url_}/stable/",
+ }
+ )
+
+for version_dir_ in sorted(output_dir_.iterdir(), reverse=True):
+ if not version_dir_.is_dir():
+ continue
+ version_name_ = version_dir_.name
+ if version_name_ in {"_static", "stable"}:
+ continue
+ if not (version_dir_ / "index.html").exists():
+ continue
+ entries_.append(
+ {
+ "name": version_name_,
+ "version": version_name_,
+ "url": f"{base_url_}/{version_name_}/",
+ }
+ )
+
+(output_dir_ / "_static").mkdir(parents=True, exist_ok=True)
+(output_dir_ / "_static" / "switcher.json").write_text(
+ json.dumps(entries_, indent=2) + "\n",
+ encoding="utf-8",
+)
+
+(output_dir_ / ".nojekyll").write_text("", encoding="utf-8")
+(output_dir_ / "index.html").write_text(
+ """
+
+
+
+
+
+
pyTorchAutoForge Documentation
+
+
+
Open stable documentation
+
+
+""",
+ encoding="utf-8",
+)
+PY
diff --git a/doc/conf.py b/doc/conf.py
index 090b7e3..8bc3b13 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -1,111 +1,155 @@
-# Configuration file for the Sphinx documentation builder.
-#
-# For the full list of built-in configuration values, see the documentation:
-# https://www.sphinx-doc.org/en/master/usage/configuration.html
-
-import os, sys, runpy
-sys.path.insert(0, os.path.abspath('../pyTorchAutoForge/')) # Add project root to path
-
-# Determine the path to the _version.py file
-version_file_path = os.path.join(os.path.dirname(__file__), '..', '_version.py')
-
-# Execute the _version.py file and retrieve the __version__ variable
-version_info = runpy.run_path(version_file_path)
-
-# Only mock when on RTD
-on_rtd = os.environ.get('READTHEDOCS') == 'True'
-
-if on_rtd:
-
- from unittest.mock import MagicMock
-
- MOCK_MODULES = [
- 'pycuda', # if needed
- 'pycuda.driver',
- 'pycuda.autoinit',
- 'pynvml',
- 'pynvml.nvmlInit',
- 'pynvml.nvmlDeviceGetHandleByIndex',
- 'pynvml.nvmlDeviceGetMemoryInfo',
- 'pynvml.nvmlShutdown',
- 'yaml', # Added for YAML file parsing
- ]
- for mod_name in MOCK_MODULES:
- sys.modules[mod_name] = MagicMock()
-
- autodoc_mock_imports = MOCK_MODULES
-
-# -- Project information -----------------------------------------------------
-# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
-
-project = 'pyTorchAutoForge'
-copyright = '2025, Pietro Califano'
-author = 'Pietro Califano'
-email = 'petercalifano.gs@gmail.com'
-version: str = version_info['__version__'] # Major.Minor.Patch
-
-# -- General configuration ---------------------------------------------------
-# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+from __future__ import annotations
-extensions = [
- "sphinx.ext.autodoc",
- "sphinx.ext.napoleon",
- "sphinx.ext.autosummary",
- "sphinx.ext.viewcode",
- "sphinx.ext.intersphinx",
- "sphinx.ext.githubpages",
- 'sphinx_rtd_theme'
-]
+import os
+from pathlib import Path
+import sys
+import logging
-templates_path = ['_templates']
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+REPO_ROOT = Path(__file__).resolve().parents[1]
+sys.path.insert(0, str(REPO_ROOT))
-# -- Options for HTML output -------------------------------------------------
-# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
-html_theme = 'sphinx_rtd_theme'
+class _AutoapiPlaceholderWarningFilter(logging.Filter):
+ def filter(self, record: logging.LogRecord) -> bool:
+ return "Unknown type: placeholder" not in record.getMessage()
-# Path to the logo image
-html_logo = "_static/ptaf_logo_small.jpg"
-html_theme_options = {
- #'analytics_id': 'G-XXXXXXXXXX', # Provided by Google in your dashboard
- #'analytics_anonymize_ip': False,
- 'logo_only': False,
- 'display_version': True,
- #'prev_next_buttons_location': 'bottom',
- #'style_external_links': False,
- #'vcs_pageview_mode': '',
- #'style_nav_header_background': '#2980B9',
- # Toc options
- #'collapse_navigation': True,
- #'sticky_navigation': True,
- #'navigation_depth': 4,
- #'includehidden': True,
- #'titles_only': False
-}
+logging.getLogger("autoapi._mapper").addFilter(_AutoapiPlaceholderWarningFilter())
+
+try:
+ from autoapi._mapper import Mapper
-# Add custom static files (such as style sheets)
-html_static_path = ['_static']
+ _AUTOAPI_CREATE_CLASS = Mapper.create_class
-# Add custom CSS
+ def _CreateClassSkippingPlaceholders(self: Mapper, data: dict, options: object = None):
+ if data.get("type") == "placeholder":
+ return
+ yield from _AUTOAPI_CREATE_CLASS(self, data, options=options)
+ Mapper.create_class = _CreateClassSkippingPlaceholders
+except Exception:
+ pass
-def setup(app):
- app.add_css_file('ptaf_doc_theme.css')
+project = "pyTorchAutoForge"
+author = "Pietro Califano"
+copyright = "2026, Pietro Califano"
+extensions = [
+ "myst_parser",
+ "sphinx.ext.autodoc",
+ "sphinx.ext.autosummary",
+ "sphinx.ext.intersphinx",
+ "sphinx.ext.napoleon",
+ "sphinx.ext.todo",
+ "sphinx.ext.viewcode",
+ "sphinx_copybutton",
+ "autoapi.extension",
+]
-# -- Options for autodoc extensions -----------------------------------------------------
-napoleon_google_docstring = True # Enable Google-style
-napoleon_numpy_docstring = True # Enable NumPy-style
-napoleon_include_init_with_doc = False # Don't include __init__ docstring
-napoleon_use_param = True # Use :param: for function params
-napoleon_use_rtype = True # Use :rtype: for return type
+source_suffix = {
+ ".rst": "restructuredtext",
+ ".md": "markdown",
+}
+master_doc = "index"
+exclude_patterns = [
+ "_build",
+ "_autoapi_templates",
+ "_autoapi_templates/**",
+ "Thumbs.db",
+ ".DS_Store",
+ "developments",
+ "developments/**",
+ "local_guides",
+ "local_guides/**",
+]
+suppress_warnings = [
+ "autoapi",
+ "autoapi.python_import_resolution",
+ "docutils",
+ "toc.not_included",
+]
-#autodoc_member_order = 'bysource'
-autosummary_generate = True # Generate .rst files for all modules
+myst_enable_extensions = [
+ "colon_fence",
+ "deflist",
+]
-# Interspinx mapping for cross-referencing
-intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
+autodoc_typehints = "description"
+autodoc_member_order = "bysource"
+autosummary_generate = True
+
+napoleon_google_docstring = True
+napoleon_numpy_docstring = True
+napoleon_include_init_with_doc = False
+napoleon_include_private_with_doc = False
+napoleon_include_special_with_doc = False
+napoleon_use_admonition_for_examples = False
+napoleon_use_admonition_for_notes = False
+napoleon_use_admonition_for_references = False
+napoleon_use_ivar = False
+napoleon_use_param = True
+napoleon_use_rtype = True
+napoleon_preprocess_types = True
+
+autoapi_type = "python"
+autoapi_dirs = [str(REPO_ROOT / "pyTorchAutoForge")]
+autoapi_root = "api/generated"
+autoapi_template_dir = "_autoapi_templates"
+autoapi_add_toctree_entry = False
+autoapi_keep_files = False
+autoapi_member_order = "bysource"
+autoapi_options = [
+ "members",
+ "undoc-members",
+ "show-inheritance",
+ "show-module-summary",
+]
+autoapi_ignore = [
+ "*/.deprecated/*",
+ "*/.experimental/*",
+ "*/.experimental.py",
+ "*/api/telegram/*",
+ "*/extra/*",
+ "*/programs/*",
+ "*/tensorboard/*",
+ "*/model_building/factories/*",
+ "*/utils/pytest_test.py",
+ "*/utils/test_fixtures/*",
+]
+intersphinx_mapping = {
+ "python": ("https://docs.python.org/3", None),
+ "numpy": ("https://numpy.org/doc/stable/", None),
+ "torch": ("https://docs.pytorch.org/docs/stable/", None),
+ "sklearn": ("https://scikit-learn.org/stable/", None),
+}
+html_theme = "pydata_sphinx_theme"
+html_logo = "assets/ptaf_logo_small.jpg"
+html_static_path = ["_static"]
+html_title = "pyTorchAutoForge"
+html_baseurl = os.environ.get("PTAF_DOC_BASE_URL", "")
+doc_version = os.environ.get("PTAF_DOC_VERSION", "stable")
+switcher_json_url = os.environ.get("PTAF_DOC_SWITCHER_JSON_URL", "_static/switcher.json")
+html_theme_options = {
+ "show_toc_level": 2,
+ "navbar_align": "left",
+ "navigation_depth": 3,
+ "collapse_navigation": False,
+ "switcher": {
+ "json_url": switcher_json_url,
+ "version_match": doc_version,
+ },
+ "navbar_end": [
+ "theme-switcher",
+ "version-switcher",
+ "navbar-icon-links",
+ ],
+ "icon_links": [
+ {
+ "name": "GitHub",
+ "url": "https://github.com/PeterCalifano/pyTorchAutoForge",
+ "icon": "fa-brands fa-github",
+ },
+ ],
+}
diff --git a/doc/datasets.md b/doc/datasets.md
new file mode 100644
index 0000000..845dfb5
--- /dev/null
+++ b/doc/datasets.md
@@ -0,0 +1,101 @@
+# Datasets
+
+```{admonition} Codex-generated note
+:class: note
+
+This guide is partially generated by Codex and should be reviewed against the current source before treating examples as authoritative.
+```
+
+## When to use this module
+
+Use {mod}`pyTorchAutoForge.datasets` for data containers and preprocessing pieces that are shared across PTAF training, evaluation, and augmentation flows. Stable surfaces include dataloader indexing, image-label dataset containers, label serialization, image augmentation helpers, and keyed vector error models.
+
+Prefer this module when:
+
+- train, validation, and optional test dataloaders should move together through the pipeline;
+- labels need a typed container with YAML-compatible keys;
+- image augmentations must update geometric labels consistently;
+- synthetic error models should perturb a known 1D label layout.
+
+## Minimal example
+
+```python
+import torch
+from torch.utils.data import DataLoader, TensorDataset
+
+from pyTorchAutoForge.datasets import DataloaderIndex, LabelsContainer
+from pyTorchAutoForge.datasets.LabelsClasses import PTAF_Datakey
+from pyTorchAutoForge.datasets.vector_error_models.VectorErrorsBaseClasses import (
+ DistributionType,
+ UniformParams,
+ Vector1dErrorModel,
+ Vector1dErrorStackModel,
+)
+
+features_ = torch.arange(12, dtype=torch.float32).reshape(6, 2)
+targets_ = torch.zeros(6, 1)
+
+train_loader_ = DataLoader(TensorDataset(features_[:4], targets_[:4]), batch_size=2)
+valid_loader_ = DataLoader(TensorDataset(features_[4:], targets_[4:]), batch_size=2)
+loader_index_ = DataloaderIndex(train_loader_, valid_loader_)
+
+labels_ = LabelsContainer()
+labels_.geometric.bound_box_coordinates = (10.0, 20.0, 30.0, 40.0)
+
+bbox_error_ = Vector1dErrorModel(
+ variable_name="bbox_bias",
+ shape=(4,),
+ error_type=DistributionType.UNIFORM,
+ parameters=UniformParams(low=0.0, high=0.0),
+ target_keys=(PTAF_Datakey.BBOX_XYWH,),
+)
+error_stack_ = Vector1dErrorStackModel(
+ keys=(PTAF_Datakey.BBOX_XYWH,),
+ error_models=(bbox_error_,),
+)
+
+corrected_bbox_, applied_error_ = error_stack_.apply(
+ torch.tensor(labels_.BBOX_XYWH),
+ return_error=True,
+)
+
+print(len(loader_index_.getTrainLoader().dataset), len(loader_index_.getValidationLoader().dataset))
+print(corrected_bbox_.tolist())
+print(applied_error_.abs().sum().item())
+```
+
+Expected output:
+
+```text
+4 2
+[10.0, 20.0, 30.0, 40.0]
+0.0
+```
+
+## Common workflow
+
+1. Build or load image/label datasets, then wrap train and validation loaders in `DataloaderIndex`.
+2. Store labels in `LabelsContainer` when YAML serialization or PTAF datakey access is needed.
+3. Configure `AugmentationConfig` with Kornia `DataKey` values before using `ImageAugmentationsHelper`.
+4. Request transform metadata from image augmentations when downstream labels must be updated from the composed geometric transform.
+5. Use `Vector1dErrorStackModel` for synthetic vector perturbations where each slice is owned by a `PTAF_Datakey`.
+
+## API links
+
+- {mod}`pyTorchAutoForge.datasets`
+- {mod}`pyTorchAutoForge.datasets.DatasetClasses`
+- {mod}`pyTorchAutoForge.datasets.DataloaderIndex`
+- {mod}`pyTorchAutoForge.datasets.LabelsClasses`
+- {mod}`pyTorchAutoForge.datasets.ImagesAugmentation`
+- {mod}`pyTorchAutoForge.datasets.vector_error_models.VectorErrorsBaseClasses`
+- {class}`pyTorchAutoForge.datasets.DataloaderIndex.DataloaderIndex`
+- {class}`pyTorchAutoForge.datasets.LabelsClasses.LabelsContainer`
+- {class}`pyTorchAutoForge.datasets.ImagesAugmentation.AugmentationConfig`
+- {class}`pyTorchAutoForge.datasets.ImagesAugmentation.ImageAugmentationsHelper`
+- {class}`pyTorchAutoForge.datasets.vector_error_models.VectorErrorsBaseClasses.Vector1dErrorStackModel`
+
+## Notes and limitations
+
+- Dataset loaders still depend on project-specific folder layout and label conventions. Validate paths and datakeys before long data scans.
+- Image augmentation examples may require Kornia-compatible image/keypoint shapes. Start with no-op probabilities, then enable one transform at a time.
+- Vector error models assume the last tensor dimension matches the configured keyed layout.
diff --git a/doc/developments/explainability_module.md b/doc/developments/explainability_module.md
new file mode 100644
index 0000000..48f9239
--- /dev/null
+++ b/doc/developments/explainability_module.md
@@ -0,0 +1,199 @@
+# PTAF Explainability Module Implementation Plan
+
+Scope: replace the current `ModelExplainer` prototype with a typed, modular explainability subsystem. Current implementation target is CPU-friendly Torch tabular models with SHAP and Captum adapters. Future targets include ONNX Runtime, TensorRT, optimized/quantized engines, recurrent models, spiking models, image inputs, sequence inputs, event inputs, and optional custom C++/CUDA acceleration.
+
+## Stage 0 - Repository, Docs, External Usage Inventory
+
+- [x] Treat `doc/` as canonical documentation folder.
+- [x] Confirm `doc/` is the canonical documentation source folder.
+- [x] Confirm active documentation config lives in `.github/workflows/docs_pages.yml` and `doc/conf.py`.
+- [x] Create this implementation plan at `doc/developments/explainability_module.md`.
+- [x] Keep old `doc/developments/model_explainer.md` as historical design context until final cleanup decision.
+- [x] Search PTAF for old explainer API references.
+- [x] Search local workspace for `mlgears`; no matching checkout was found under checked workspace paths, so no external upgrade can be applied in this implementation pass.
+- [x] Re-run API usage search after implementation and before final validation.
+
+## Stage 1 - Typed Core Package
+
+- [x] Create `pyTorchAutoForge/evaluation/explainability/`.
+- [x] Add `schemas.py` with:
+ - [x] `ExplainerTaskType`
+ - [x] `ExplainerBackendType`
+ - [x] `ExplainerMethod`
+ - [x] `ExplainerInputSpec`
+ - [x] `ExplainerOutputSpec`
+- [x] Add `method_configs.py` with typed method config dataclasses:
+ - [x] `ExplainerBaselineConfig`
+ - [x] `ShapExplainerConfig`
+ - [x] `CaptumIntegratedGradientsConfig`
+ - [x] `CaptumSaliencyConfig`
+ - [x] `CaptumGradientShapConfig`
+- [x] Add validation for all config objects before backend library calls.
+- [x] Add `resolve_method_name` and avoid all free-form `method_kwargs` style APIs.
+- [x] Use names that explicitly belong to explainer subsystem and avoid ambiguous public names like `InputSpec`, `OutputSpec`, `TargetSpec`, `BaselineConfig`, and `PredictorProtocol`.
+
+Validation gate:
+
+- [x] `conda run -n autoforge pytest -q tests/evaluation/explainability/test_method_configs.py`
+
+## Stage 2 - Request, Target, Result Objects
+
+- [x] Add `targets.py` with `ExplainerTargetSpec`.
+- [x] Add `configs.py` with `ExplainerConfig` and `ExplainerRequest`.
+- [x] Add `results.py` with `ExplainerResult`.
+- [x] Keep constructors side-effect-light.
+- [x] Create output folders only in `ExplainerResult.save`.
+- [x] Store arrays in `.npz` and metadata in `.json`.
+- [x] Do not serialize native SHAP/Captum raw objects by default.
+- [x] Keep `raw` available in memory for native backend objects.
+
+Validation gate:
+
+- [x] `conda run -n autoforge pytest -q tests/evaluation/explainability/test_target_spec.py tests/evaluation/explainability/test_explanation_result.py`
+
+## Stage 3 - Predictors
+
+- [x] Add `predictors.py` with:
+ - [x] `ExplainerPredictorProtocol`
+ - [x] `TorchExplainerPredictor`
+ - [x] `CallableExplainerPredictor`
+- [x] Support tensor, tuple/list tensor, and dict tensor inputs for Torch prediction.
+- [x] Preserve model training/eval state around prediction.
+- [x] Provide `predict_with_grad` for gradient explainers.
+- [x] Add TODO for runtime-backed predictors wrapping `ModelRuntimeApi` for future ONNX Runtime and TensorRT black-box explainers.
+
+Validation gate:
+
+- [x] `conda run -n autoforge pytest -q tests/evaluation/explainability/test_torch_predictor.py`
+
+## Stage 4 - Registry And Dispatch
+
+- [x] Add `registry.py` with:
+ - [x] `ExplainerAdapterProtocol`
+ - [x] `ExplainerRegistry`
+ - [x] `DEFAULT_EXPLAINER_REGISTRY`
+ - [x] `register_default_explainers`
+- [x] Add `explainer.py` with `ModelExplainer.explain`.
+- [x] Resolve method from typed method config, not from arbitrary string options.
+- [x] Instantiate adapters lazily through registry.
+- [x] Leave `auto_plot` as no-op metadata warning for now.
+- [x] Save outputs through `ExplainerResult.save` only when `save_outputs=True`.
+
+Validation gate:
+
+- [x] `conda run -n autoforge pytest -q tests/evaluation/explainability/test_explainer_registry.py`
+
+## Stage 5 - SHAP Adapter
+
+- [x] Add `adapters/shap_adapter.py`.
+- [x] Lazy import `shap` inside adapter methods.
+- [x] Support tabular Torch tensor, NumPy array, and pandas DataFrame inputs.
+- [x] Infer DataFrame feature names when explicit `ExplainerInputSpec.feature_names` is absent.
+- [x] Use explicit background data when supplied.
+- [x] Sample background from inputs when missing, capped by `max_background_samples`.
+- [x] Use `TorchExplainerPredictor` or callable predictor.
+- [x] Apply `ExplainerTargetSpec` when target selection is simple and clear.
+- [x] Return normalized `ExplainerResult`.
+- [x] Keep native SHAP explanation in `raw["shap_explanation"]`.
+- [x] Metadata includes method, backend, algorithm, link, background size, seed.
+
+Validation gate:
+
+- [x] `conda run -n autoforge pytest -q tests/evaluation/explainability/test_shap_adapter.py`
+
+## Stage 6 - Captum Adapter
+
+- [x] Add `adapters/captum_adapter.py`.
+- [x] Lazy import `captum` inside adapter methods.
+- [x] Support simple Torch tensor inputs for first implementation.
+- [x] Implement:
+ - [x] Integrated Gradients
+ - [x] Saliency
+ - [x] GradientShap
+- [x] Implement typed baseline strategies:
+ - [x] zero
+ - [x] mean
+ - [x] constant
+ - [x] tensor
+ - [x] background
+- [x] Use `ExplainerTargetSpec.target_index` where Captum supports it.
+- [x] Preserve convergence deltas only for methods that return them.
+- [x] Return normalized `ExplainerResult`.
+
+Validation gate:
+
+- [x] `conda run -n autoforge pytest -q tests/evaluation/explainability/test_captum_adapter.py`
+
+## Stage 7 - Exports, Facade, Dependency Metadata
+
+- [x] Update `pyTorchAutoForge/evaluation/explainability/__init__.py`.
+- [x] Update `pyTorchAutoForge/evaluation/__init__.py`.
+- [x] Replace `pyTorchAutoForge/evaluation/ModelExplainer.py` with lightweight re-export facade.
+- [x] Stop exporting old `ModelExplainerHelper`, `CaptumExplainMethods`, and `ShapExplainMethods`.
+- [x] Add optional extras in `pyproject.toml`:
+ - [x] `explain-shap`
+ - [x] `explain-captum`
+ - [x] `explain`
+- [x] Ensure package import has no import-time dependency on SHAP, Captum, or TensorRT.
+
+Validation gate:
+
+- [x] `conda run -n autoforge pytest -q tests/api/test_import_safety.py tests/evaluation/explainability`
+
+## Stage 8 - Runnable CPU Example
+
+- [x] Add `examples/example_ModelExplainer.py`.
+- [x] Use deterministic `DummyTestModel`.
+- [x] Run SHAP block only when SHAP is installed.
+- [x] Run Captum Integrated Gradients block only when Captum is installed.
+- [x] Print result shapes and metadata.
+- [x] Keep example CPU-only and short.
+
+Validation gate:
+
+- [x] `conda run -n autoforge python examples/example_ModelExplainer.py`
+
+## Stage 9 - Performance Work For Later Stages
+
+- [ ] Add timing metadata hooks around adapter execution once functional API stabilizes.
+- [ ] Profile SHAP perturbation cost by sample count, feature count, and background size.
+- [ ] Profile Captum gradient methods by input size, target count, and integration step count.
+- [ ] Add batch-size tuning for SHAP model wrapper calls.
+- [ ] Add vectorized target selection for multi-output Torch predictions.
+- [ ] Add runtime-backed black-box perturbation adapter that wraps `ModelRuntimeApi`.
+- [ ] Add ONNX Runtime predictor wrapper before any ONNX-specific attribution claims.
+- [ ] Add TensorRT predictor wrapper before any TensorRT-specific attribution claims.
+- [ ] Prototype custom C++/CUDA direct kernels only after profiling identifies a hot path that Python/Captum/SHAP cannot handle.
+- [ ] Candidate custom acceleration areas:
+ - [ ] baseline tensor generation
+ - [ ] perturbation mask application
+ - [ ] batched feature ablations
+ - [ ] aggregation of attribution statistics
+ - [ ] event/sequence temporal window selection
+- [ ] Keep C++/CUDA extensions optional with CPU fallback and parity tests.
+
+## Stage 10 - Final Validation
+
+- [x] `conda run -n autoforge pytest -q tests/evaluation/explainability`
+- [x] `conda run -n autoforge python examples/example_ModelExplainer.py`
+- [x] `conda run -n autoforge pytest -q tests/api/test_import_safety.py`
+- [x] `conda run -n autoforge pytest -q tests/api/onnx/test_ModelHandlerONNx.py tests/api/tensorrt/test_TRTengineExporter.py`
+- [x] Confirm no public `method_kwargs` field exists.
+- [x] Confirm no arbitrary method option dictionary exists in public explainer API.
+- [x] Confirm method options are typed dataclasses.
+- [x] Confirm invalid options fail in validation.
+- [x] Confirm old ONNX/TensorRT runtime behavior is not changed.
+
+## Non-Goals In First Implementation
+
+- [ ] Full image explainability.
+- [ ] Full sequence explainability.
+- [ ] Full event explainability.
+- [ ] Full recurrent state policy.
+- [ ] Full spiking attribution.
+- [ ] Full ONNX Runtime attribution.
+- [ ] Full TensorRT attribution.
+- [ ] Plot/report generation.
+- [ ] MLflow logging.
+- [ ] Native SHAP/Captum raw object serialization.
+- [ ] Refactors to unrelated modules.
diff --git a/doc/developments/model_explainer.md b/doc/developments/model_explainer.md
new file mode 100644
index 0000000..c72fe9c
--- /dev/null
+++ b/doc/developments/model_explainer.md
@@ -0,0 +1,173 @@
+# Model Explainer Implementation Plan
+
+Status date: 2026-05-17
+
+Scope: feature-level model explanation in PTAF for PyTorch models, first for tabular/vector inputs and then image/CNN workflows. Main public entry point remains `ModelExplainerHelper`, with typed configuration and result containers added around the existing helper-style API.
+
+## Current Status
+
+- [x] Existing prototype reviewed for integration risks.
+- [x] Heavy optional backend imports moved out of module import path.
+- [x] `pyTorchAutoForge.evaluation` namespace converted to lazy exports.
+- [x] Captum call contract fixed for target selection.
+- [x] Basic SHAP tabular path stabilized with deterministic background selection.
+- [x] Focused regression tests added for import laziness, Captum call shape, SHAP background selection, and result serialization.
+- [x] Real Captum and SHAP tests added in `autoforge`.
+- [x] Full default test suite passes in `autoforge`.
+
+## Stage 1 - API Contract And Integration Baseline
+
+- [x] Keep `ModelExplainerHelper` as backward-compatible public entry point.
+- [x] Keep `explain_features()` return shape compatible with old dict consumers.
+- [x] Add typed result objects:
+ - [x] `FeatureAttributionStats`
+ - [x] `CaptumExplainerResult`
+ - [x] `ShapExplainerResult`
+- [x] Add `ModelExplainerConfig` for new configuration fields.
+- [x] Preserve legacy `features_names` argument.
+- [x] Add preferred `feature_names` argument.
+- [x] Keep `CaptumExplainMethods` and `ShapExplainMethods` exported.
+- [ ] Add deprecation warning for `features_names` after downstream callers are checked.
+- [ ] Decide whether `task_type` should remain accepted but unused, or become part of backend selection.
+- [ ] Add API reference examples in the Sphinx API page.
+
+## Stage 2 - Import Safety And Optional Dependencies
+
+- [x] Remove top-level `captum`, `shap`, `torch`, `seaborn`, and `matplotlib.pyplot` imports from `ModelExplainer.py`.
+- [x] Load `torch` only when helper instance is created.
+- [x] Load `captum.attr` only when Captum backend is selected.
+- [x] Load `shap` only when SHAP backend is selected.
+- [x] Load plot packages only when plotting is requested.
+- [x] Make `pyTorchAutoForge.evaluation.__init__` lazy.
+- [x] Add import-safety regression tests.
+- [ ] Split backend dependency errors by backend:
+ - [ ] Captum missing should not mention SHAP.
+ - [ ] SHAP missing should not mention Captum.
+- [ ] Consider moving `shap` and `captum` to optional extras if package install weight becomes too high.
+
+## Stage 3 - Captum Backend
+
+- [x] Support `IntegratedGradients`.
+- [x] Support `Saliency`.
+- [x] Support `GradientShap`.
+- [x] Pass target index as `target=...`.
+- [x] Return convergence delta when backend supports it.
+- [x] Add default zero baseline for `GradientShap`.
+- [x] Allow configured baseline for Integrated Gradients / Gradient SHAP.
+- [x] Convert tensor attributions to numpy result arrays.
+- [x] Compute signed mean, absolute mean, standard deviation, quantiles, and min/max.
+- [x] Flatten non-batch feature dimensions for stats.
+- [x] Add regression tests using fake Captum backend.
+- [x] Run real Captum Integrated Gradients analytic test in `autoforge`.
+- [x] Run real Captum Saliency analytic test in `autoforge`.
+- [x] Add real Captum unit tests gated by installed dependency marker.
+- [ ] Add image/CNN Captum attribution mode:
+ - [ ] input validation for `NCHW`.
+ - [ ] per-channel aggregation.
+ - [ ] pixel/heatmap output.
+ - [ ] optional layer attribution.
+- [ ] Add support for `LayerIntegratedGradients`.
+- [ ] Add support for `GuidedBackprop` or equivalent image-oriented method.
+- [ ] Add batch-size control for large attribution jobs.
+
+## Stage 4 - SHAP Backend
+
+- [x] Support tabular/vector SHAP path.
+- [x] Validate SHAP input as `(num_samples, num_features)`.
+- [x] Use deterministic background sample selection.
+- [x] Add configurable background fraction.
+- [x] Add configurable background max sample count.
+- [x] Forward PyTorch model through numpy wrapper under `torch.no_grad()`.
+- [x] Normalize scalar/vector outputs to `(num_samples, num_outputs)`.
+- [x] Preserve feature and output names.
+- [x] Add fake-SHAP regression test for background selection and output shape.
+- [x] Add `shap` to project dependencies because backend is public.
+- [x] Install/verify SHAP in active development environment.
+- [x] Add real SHAP smoke test for vector-to-vector regression.
+- [x] Add real SHAP smoke test for scalar regression.
+- [ ] Add classifier-logit vs probability mode decision.
+- [ ] Add warning if model is in training mode before explainer switches to eval.
+- [ ] Add explicit unsupported error for image SHAP until CNN path is designed.
+
+## Stage 5 - Artifacts And Plots
+
+- [x] Replace pandas/PyTables `.h5` write path with compressed `.npz`.
+- [x] Save SHAP values, base values, data, feature names, and output names.
+- [x] Return artifact path in result object.
+- [x] Save Captum feature-importance plot when `auto_plot=True`.
+- [x] Use seaborn for Captum bar plot when available.
+- [x] Fall back to matplotlib when seaborn is unavailable.
+- [x] Fix SHAP multi-output plot bookkeeping so figures from all outputs are retained.
+- [x] Close figures by default after saving.
+- [ ] Add configurable artifact format enum:
+ - [ ] `NPZ`
+ - [ ] `HDF5`
+ - [ ] `CSV_SUMMARY`
+- [ ] Add summary CSV export for Captum stats.
+- [ ] Add stable file naming scheme for repeated runs.
+- [ ] Add optional MLflow artifact logging hook.
+- [ ] Add plot tests that verify files exist without checking visual content.
+
+## Stage 6 - Tests And CI Coverage
+
+- [x] Restore `tests/evaluation/test_ModelExplainer.py` from empty placeholder into real tests.
+- [x] Test lazy import of explainer backend dependencies.
+- [x] Test lazy import of evaluation namespace.
+- [x] Test feature-stat flattening.
+- [x] Test Captum `target` keyword behavior.
+- [x] Test Captum default baseline behavior for Gradient SHAP.
+- [x] Test SHAP deterministic background selection.
+- [x] Test `.npz` artifact content for SHAP fake backend.
+- [x] Test feature-name validation.
+- [x] Run targeted evaluation/import safety suite.
+- [x] Run full default pytest suite and record unrelated failures.
+- [x] Add dependency-marked real Captum tests.
+- [x] Add dependency-marked real SHAP tests.
+- [ ] Add no-plot mode test for headless CI.
+- [ ] Add plot-save smoke test with matplotlib `Agg`.
+- [x] Add shape tests for multi-output regression.
+- [x] Add shape tests for single-output regression.
+- [ ] Add classification target tests.
+
+## Stage 7 - Examples And Documentation
+
+- [ ] Add runnable example for Captum vector regression.
+- [ ] Add runnable example for Captum vector classification.
+- [ ] Add runnable example for SHAP vector regression.
+- [ ] Add example output snippets in docs.
+- [ ] Add explainer API page to Sphinx navigation.
+- [ ] Add docs section explaining backend dependency requirements.
+- [ ] Add docs section explaining artifact outputs.
+- [ ] Add docs section explaining known limitations:
+ - [ ] SHAP currently tabular/vector only.
+ - [ ] image/CNN attribution planned but not complete.
+ - [ ] results are explanation artifacts, not ONNX-exportable runtime code.
+
+## Stage 8 - Advanced Roadmap
+
+- [ ] Add image/CNN attribution support.
+- [ ] Add layer-wise Captum explainers.
+- [ ] Add segmentation explanation workflow.
+- [ ] Add multi-input model support.
+- [ ] Add model wrapper support for PTAF runtime APIs.
+- [ ] Add MLflow integration for explainer artifacts.
+- [ ] Add explainer report generator.
+- [ ] Add dataset sampling utility for explanation cohorts.
+- [ ] Add feature-group attribution support.
+- [ ] Add uncertainty-aware attribution summaries for dropout ensembles.
+
+## Validation Ledger
+
+- [x] `python3 -m py_compile pyTorchAutoForge/evaluation/ModelExplainer.py pyTorchAutoForge/evaluation/__init__.py tests/evaluation/test_ModelExplainer.py`
+- [x] `conda run -n autoforge pytest -q tests/evaluation/test_ModelExplainer.py`
+- [x] `conda run -n autoforge pytest -q tests/api/test_import_safety.py tests/evaluation/test_ResultsPlotter.py tests/evaluation/test_ModelProfiler.py tests/evaluation/test_ModelExplainer.py`
+- [x] Real Captum Integrated Gradients analytic test in `autoforge`.
+- [x] Real Captum Saliency analytic test in `autoforge`.
+- [x] Real SHAP scalar-regression additivity test in `autoforge`.
+- [x] Real SHAP vector-regression additivity test in `autoforge`.
+- [x] `conda run -n autoforge pytest -q`
+
+## Known Open Issues
+
+- [ ] `pyproject.toml` now lists `shap`; lock/install scripts may need matching update if this repo treats them as source-of-truth.
+- [ ] Current image/CNN explainer tasks remain planned, not implemented.
diff --git a/doc/evaluation.md b/doc/evaluation.md
new file mode 100644
index 0000000..825b217
--- /dev/null
+++ b/doc/evaluation.md
@@ -0,0 +1,88 @@
+# Evaluation
+
+```{admonition} Codex-generated note
+:class: note
+
+This guide is partially generated by Codex and should be reviewed against the current source before treating examples as authoritative.
+```
+
+## When to use this module
+
+Use {mod}`pyTorchAutoForge.evaluation` after a model has produced predictions and targets. The stable guide-level surface covers regression evaluation, prediction-error plots, profiling helpers, and the separate explainability subsystem.
+
+Prefer this module when:
+
+- residual statistics should be computed from a validation dataloader;
+- prediction errors should be plotted with Matplotlib or Seaborn;
+- a quick Torch profiler trace is needed for model inference;
+- SHAP or Captum explanations should be run through the typed explainer API.
+
+## Minimal example
+
+```python
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+import matplotlib
+matplotlib.use("Agg")
+import numpy as np
+
+from pyTorchAutoForge.evaluation import ResultsPlotterConfig, ResultsPlotterHelper
+from pyTorchAutoForge.evaluation.ResultsPlotter import backend_module
+
+stats_ = {
+ "prediction_err": np.array([[0.1, -0.2], [0.0, 0.3], [-0.1, 0.1]]),
+ "mean_prediction_err": np.array([0.0, 0.0667]),
+}
+
+with TemporaryDirectory() as tmp_dir_:
+ plotter_ = ResultsPlotterHelper(
+ stats=stats_,
+ backend_module_=backend_module.SEABORN,
+ config=ResultsPlotterConfig(
+ save_figs=True,
+ entriesNames=["x", "y"],
+ units=["m", "m"],
+ output_folder=str(Path(tmp_dir_) / "plots"),
+ ),
+ )
+ plotter_.histPredictionErrors()
+
+ print(Path(plotter_.output_folder, "prediction_errors_all_components.png").is_file())
+ print(stats_["prediction_err"].shape)
+```
+
+Expected output, ignoring informational output-folder logs:
+
+```text
+True
+(3, 2)
+```
+
+## Common workflow
+
+1. Evaluate a regression model with `ModelEvaluator` when predictions, targets, losses, and residual statistics should be computed from a dataloader.
+2. Pass saved or computed stats into `ResultsPlotterHelper` for prediction-error histograms.
+3. Profile candidate models with `ModelProfilerHelper` before export or deployment.
+4. Use [](model_explainer.md) for SHAP/Captum feature-attribution workflows.
+5. Keep plotting backends headless in CI by setting Matplotlib to `Agg` or by saving figures directly.
+
+## API links
+
+- {mod}`pyTorchAutoForge.evaluation`
+- {mod}`pyTorchAutoForge.evaluation.ModelEvaluator`
+- {mod}`pyTorchAutoForge.evaluation.ResultsPlotter`
+- {mod}`pyTorchAutoForge.evaluation.ModelProfiler`
+- {mod}`pyTorchAutoForge.evaluation.explainability`
+- {class}`pyTorchAutoForge.evaluation.ModelEvaluator.ModelEvaluator`
+- {class}`pyTorchAutoForge.evaluation.ModelEvaluator.ModelEvaluatorConfig`
+- {class}`pyTorchAutoForge.evaluation.ResultsPlotter.ResultsPlotterHelper`
+- {class}`pyTorchAutoForge.evaluation.ResultsPlotter.ResultsPlotterConfig`
+- {class}`pyTorchAutoForge.evaluation.ModelProfiler.ModelProfilerHelper`
+- {class}`pyTorchAutoForge.evaluation.explainability.ModelExplainer`
+
+## Notes and limitations
+
+- `ResultsPlotterHelper.histPredictionErrors()` expects `prediction_err` in the stats dictionary and uses `mean_prediction_err` when present.
+- `ModelProfilerHelper` prints profiler tables and can write Chrome trace files; keep paths temporary in examples and tests.
+- Explainability has optional dependencies. Install the explainability extra before running SHAP or Captum workflows.
diff --git a/doc/getting_started.md b/doc/getting_started.md
new file mode 100644
index 0000000..286b5c9
--- /dev/null
+++ b/doc/getting_started.md
@@ -0,0 +1,143 @@
+# Getting Started
+
+This page covers first install, local development setup, and basic validation.
+
+## Install From PyPI
+
+Use this path when you only need the released library:
+
+```bash
+python -m pip install pyTorchAutoForge
+```
+
+Optional feature groups are explicit:
+
+```bash
+python -m pip install "pyTorchAutoForge[explain]"
+python -m pip install "pyTorchAutoForge[classical-ml]"
+```
+
+`explain` installs SHAP and Captum support. `classical-ml` installs optional XGBoost and PySR wrappers.
+
+## Install From Source
+
+Use this path for local development or when testing unreleased changes:
+
+```bash
+git clone git@github.com:PeterCalifano/pyTorchAutoForge.git
+cd pyTorchAutoForge
+python -m pip install -e .
+```
+
+Install development extras:
+
+```bash
+python -m pip install -e ".[test,docs]"
+```
+
+Install documentation and explainer extras together:
+
+```bash
+python -m pip install -e ".[test,docs,explain]"
+```
+
+## Conda Development Environment
+
+Recommended local environment name: `autoforge`.
+
+```bash
+conda create -n autoforge python=3.12
+conda activate autoforge
+python -m pip install --upgrade pip
+python -m pip install -e ".[test,docs]"
+```
+
+Repository scripts default to `autoforge` when no conda environment is active. To use a different active environment:
+
+```bash
+conda activate my_ptaf_env
+bash doc/makedoc.sh
+```
+
+To force a specific environment:
+
+```bash
+CONDA_ENV=my_ptaf_env bash doc/makedoc.sh
+```
+
+## Verify Install
+
+Run a minimal import check:
+
+```bash
+python - <<'PY'
+import pyTorchAutoForge
+
+print("pyTorchAutoForge import ok")
+PY
+```
+
+Expected output:
+
+```text
+pyTorchAutoForge import ok
+```
+
+Check PyTorch availability separately:
+
+```bash
+python - <<'PY'
+import torch
+
+print(torch.__version__)
+print("CUDA available:", torch.cuda.is_available())
+PY
+```
+
+## Run Tests
+
+Default local test run:
+
+```bash
+./run_tests.sh -- -q
+```
+
+Use another conda environment:
+
+```bash
+./run_tests.sh -e my_ptaf_env -- -q
+```
+
+Slow, GPU, and visual tests are opt-in:
+
+```bash
+./run_tests.sh -- --run-slow -q
+./run_tests.sh -- --run-gpu -q
+./run_tests.sh -- --run-visual -q
+```
+
+## Build Documentation
+
+Build the Sphinx site:
+
+```bash
+bash doc/makedoc.sh
+```
+
+Serve locally:
+
+```bash
+bash doc/makedoc.sh -a
+```
+
+Open:
+
+```text
+http://127.0.0.1:8000
+```
+
+## Next Steps
+
+- Use the API reference for model-building, datasets, optimization, evaluation, and deployment modules.
+- Use examples under `examples/` for runnable workflows.
+- Install optional extras only for the backends you need.
diff --git a/doc/index.md b/doc/index.md
new file mode 100644
index 0000000..4a15329
--- /dev/null
+++ b/doc/index.md
@@ -0,0 +1,48 @@
+# pyTorchAutoForge
+
+```{image} assets/ptaf_logo_small.jpg
+:alt: pyTorchAutoForge logo
+:width: 180px
+```
+
+`pyTorchAutoForge` is a PyTorch-oriented toolbox for model construction, training support, experiment monitoring, and deployment through ONNX and TensorRT workflows.
+
+```{warning}
+Public APIs are still evolving. Prefer pinned releases for production work.
+```
+
+## Core Areas
+
+- Model-building blocks, backbones, adapters, and export-oriented pooling layers.
+- Dataset containers, label containers, image augmentation utilities, and vector error models.
+- Training-manager utilities, MLflow/Optuna integration, and monitoring helpers.
+- Runtime and export APIs for PyTorch, ONNX, and TensorRT deployment paths.
+
+```{toctree}
+:caption: User Guide
+:maxdepth: 2
+
+getting_started
+model_building
+datasets
+runtime_export
+training_monitoring
+evaluation
+model_explainer
+utilities
+```
+
+```{toctree}
+:caption: Reference
+:maxdepth: 2
+
+api/index
+```
+
+```{toctree}
+:caption: Development
+:maxdepth: 2
+
+testing
+roadmap
+```
diff --git a/doc/index.rst b/doc/index.rst
deleted file mode 100644
index 4ec4bde..0000000
--- a/doc/index.rst
+++ /dev/null
@@ -1,39 +0,0 @@
-.. pyTorchAutoForge documentation master file, created by
- sphinx-quickstart on Sun Sep 22 18:03:23 2024.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
-
-Welcome to pyTorchAutoForge's documentation!
-============================================
-
-.. image:: _static/ptaf_logo_small.jpg
- :alt: ptaf_logo_small
- :align: center
- :width: 200px
-
-.. toctree::
- :maxdepth: 4
- :caption: Contents:
-
-.. Add sources.
-
-.. autosummary::
- :toctree: sources/
- :recursive:
-
- pyTorchAutoForge
- pyTorchAutoForge.datasets
- pyTorchAutoForge.evaluation
- pyTorchAutoForge.hparams_optim
- pyTorchAutoForge.optimization
- pyTorchAutoForge.setup
- pyTorchAutoForge.utils
-.. pyTorchAutoForge.api
-
-Indices and tables
-==================
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
-
diff --git a/doc/install_sphinx.sh b/doc/install_sphinx.sh
deleted file mode 100644
index f98db0a..0000000
--- a/doc/install_sphinx.sh
+++ /dev/null
@@ -1 +0,0 @@
-pip install sphinx sphinx-autobuild sphinx_rtd_theme sphinxcontrib-matlabdomain --break-system-packages
\ No newline at end of file
diff --git a/doc/make.bat b/doc/make.bat
deleted file mode 100644
index 32bb245..0000000
--- a/doc/make.bat
+++ /dev/null
@@ -1,35 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=.
-set BUILDDIR=_build
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
- echo.
- echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
- echo.installed, then set the SPHINXBUILD environment variable to point
- echo.to the full path of the 'sphinx-build' executable. Alternatively you
- echo.may add the Sphinx directory to PATH.
- echo.
- echo.If you don't have Sphinx installed, grab it from
- echo.https://www.sphinx-doc.org/
- exit /b 1
-)
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-
-:end
-popd
diff --git a/doc/makedoc.sh b/doc/makedoc.sh
index e80a35e..6c10015 100755
--- a/doc/makedoc.sh
+++ b/doc/makedoc.sh
@@ -1,25 +1,78 @@
#!/bin/bash
-source ~/miniconda3/etc/profile.d/conda.sh
-conda activate autoforge
+set -euo pipefail
-while getopts "a,i:,o:,p:" opt; do
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
+cd "${REPO_ROOT}"
+
+CONDA_EXE="${CONDA_EXE:-conda}"
+CONDA_BASE="$("${CONDA_EXE}" info --base)"
+# shellcheck source=/dev/null
+source "${CONDA_BASE}/etc/profile.d/conda.sh"
+
+if [ -n "${CONDA_ENV:-}" ]; then
+ conda activate "${CONDA_ENV}"
+elif [ -n "${CONDA_DEFAULT_ENV:-}" ]; then
+ echo "Using active conda env: ${CONDA_DEFAULT_ENV}"
+else
+ conda activate autoforge
+fi
+
+ensure_docs_dependencies_() {
+ python - <<'PY'
+import importlib.util
+import sys
+
+required_modules_ = (
+ "sphinx",
+ "pydata_sphinx_theme",
+ "myst_parser",
+ "autoapi",
+ "sphinx_copybutton",
+)
+missing_modules_ = [
+ module_name_
+ for module_name_ in required_modules_
+ if importlib.util.find_spec(module_name_) is None
+]
+if missing_modules_:
+ print("Missing docs modules: " + ", ".join(missing_modules_))
+ sys.exit(1)
+PY
+}
+
+if ! ensure_docs_dependencies_; then
+ echo "Installing Sphinx documentation dependencies into conda env: ${CONDA_DEFAULT_ENV}"
+ python -m pip install -r doc/requirements.txt
+ ensure_docs_dependencies_
+fi
+
+SERVE=0
+STRICT=0
+HOST="127.0.0.1"
+PORT="8000"
+
+while getopts "ash:p:" opt; do
case $opt in
- a) AUTOBUILD=1 ;;
- i) INPUT="${OPTARG:-doc}" ;;
- o) OUTPUT="${OPTARG:-test_autodoc}" ;;
- p) PORT="${OPTARG:-8000}" ;;
+ a) SERVE=1 ;;
+ s) STRICT=1 ;;
+ h) HOST="$OPTARG" ;;
+ p) PORT="$OPTARG" ;;
*) echo "Invalid option"; exit 1 ;;
esac
done
-# Set default values if not provided
-AUTOBUILD=${AUTOBUILD:-0}
-INPUT=${INPUT:-doc}
-OUTPUT=${OUTPUT:-test_autodoc}
-PORT=${PORT:-8000}
+SPHINX_ARGS=(-b html doc site)
+if [ "$STRICT" -eq 1 ]; then
+ SPHINX_ARGS=(-W --keep-going "${SPHINX_ARGS[@]}")
+fi
+
+rm -rf site
-if [ -n "$AUTOBUILD" ]; then
- sphinx-autobuild "$INPUT" "$OUTPUT" --port "$PORT" --open-browser
+if [ "$SERVE" -eq 1 ]; then
+ python -m sphinx "${SPHINX_ARGS[@]}"
+ cd site
+ python -m http.server "${PORT}" --bind "${HOST}"
else
- make html
+ python -m sphinx "${SPHINX_ARGS[@]}"
fi
diff --git a/doc/model_building.md b/doc/model_building.md
new file mode 100644
index 0000000..0abaa5d
--- /dev/null
+++ b/doc/model_building.md
@@ -0,0 +1,91 @@
+# Model Building
+
+```{admonition} Codex-generated note
+:class: note
+
+This guide is partially generated by Codex and should be reviewed against the current source before treating examples as authoritative.
+```
+
+## When to use this module
+
+Use {mod}`pyTorchAutoForge.model_building` when the model architecture should be built from reusable Torch blocks rather than hand-written layers. The stable surface covers convolutional and fully connected blocks, export-aware pooling layers, backbone adapters, and multi-head regressors.
+
+Prefer these utilities when:
+
+- model components need consistent initialization, activation, pooling, and regularization options;
+- pooling must stay friendly to ONNX export paths;
+- a shared feature tensor feeds more than one prediction head;
+- a backbone or adapter needs to be assembled from existing PTAF building blocks.
+
+## Minimal example
+
+```python
+import torch
+
+from pyTorchAutoForge.model_building import (
+ MultiHeadRegressor,
+ TemplateFullyConnectedNet,
+ TemplateFullyConnectedNetConfig,
+)
+from pyTorchAutoForge.model_building.poolingBlocks import CustomAdaptiveAvgPool2d
+
+torch.manual_seed(0)
+
+feature_model_ = TemplateFullyConnectedNet(
+ TemplateFullyConnectedNetConfig(
+ out_channels_sizes=[8],
+ input_layer_size=4,
+ output_layer_size=8,
+ activ_type="relu",
+ regularization_layer_type="none",
+ )
+)
+
+heads_ = torch.nn.ModuleList(
+ [
+ torch.nn.Linear(8, 2),
+ torch.nn.Linear(8, 1),
+ ]
+)
+regressor_ = torch.nn.Sequential(feature_model_, MultiHeadRegressor(heads_)).eval()
+
+output_ = regressor_(torch.ones(3, 4))
+pooled_ = CustomAdaptiveAvgPool2d((1, 1))(torch.ones(3, 2, 4, 4))
+
+print(tuple(output_.shape))
+print(tuple(pooled_.shape))
+```
+
+Expected output:
+
+```text
+(3, 3)
+(3, 2, 1, 1)
+```
+
+## Common workflow
+
+1. Pick the smallest layer abstraction that matches the architecture: block, block stack, template network, backbone, or adapter.
+2. Use dataclass-like config objects for template models so constructor arguments are explicit and serializable.
+3. Add task heads through `MultiHeadRegressor` when one feature representation serves several outputs.
+4. Use PTAF pooling layers when adaptive pooling needs to survive ONNX export checks.
+5. Validate with one CPU forward pass before moving to training or export.
+
+## API links
+
+- {mod}`pyTorchAutoForge.model_building`
+- {mod}`pyTorchAutoForge.model_building.convolutionalBlocks`
+- {mod}`pyTorchAutoForge.model_building.fullyConnectedBlocks`
+- {mod}`pyTorchAutoForge.model_building.modelBuildingBlocks`
+- {mod}`pyTorchAutoForge.model_building.poolingBlocks`
+- {mod}`pyTorchAutoForge.model_building.backbones`
+- {class}`pyTorchAutoForge.model_building.modelBuildingBlocks.TemplateFullyConnectedNet`
+- {class}`pyTorchAutoForge.model_building.modelBuildingBlocks.TemplateFullyConnectedNetConfig`
+- {class}`pyTorchAutoForge.model_building.ModelAutoBuilder.MultiHeadRegressor`
+- {class}`pyTorchAutoForge.model_building.poolingBlocks.CustomAdaptiveAvgPool2d`
+
+## Notes and limitations
+
+- `ModelAssembler` and `ModelAutoBuilder` still contain experimental or incomplete paths; prefer tested blocks, template networks, adapters, and backbones for new guide-level work.
+- Tensor shapes should be checked with real sample tensors before export. Some blocks have dynamic behavior that is easiest to validate by running `torch.onnx.export` on the target architecture.
+- Factories under `model_building.factories` are implementation helpers and are not treated as tutorial entry points.
diff --git a/doc/model_explainer.md b/doc/model_explainer.md
new file mode 100644
index 0000000..2d4444f
--- /dev/null
+++ b/doc/model_explainer.md
@@ -0,0 +1,167 @@
+# Model Explainer
+
+`pyTorchAutoForge.evaluation.explainability` provides typed explainability
+requests and dispatches them to SHAP or Captum adapters.
+
+Install optional explainability dependencies first:
+
+```bash
+python -m pip install "pyTorchAutoForge[explain]"
+```
+
+## Captum Integrated Gradients
+
+Use Captum methods when you have a differentiable Torch model and tensor inputs.
+
+```python
+import torch
+
+from pyTorchAutoForge.evaluation.explainability import (
+ CaptumIntegratedGradientsConfig,
+ ExplainerBackendType,
+ ExplainerConfig,
+ ExplainerInputSpec,
+ ExplainerOutputSpec,
+ ExplainerRequest,
+ ExplainerTaskType,
+ ExplainerTargetSpec,
+ ModelExplainer,
+)
+
+model = torch.nn.Sequential(
+ torch.nn.Linear(3, 8),
+ torch.nn.ReLU(),
+ torch.nn.Linear(8, 1),
+)
+
+inputs = torch.tensor(
+ [
+ [0.2, 0.4, 0.8],
+ [0.5, 0.1, 0.3],
+ ],
+ dtype=torch.float32,
+)
+
+request = ExplainerRequest(
+ model=model,
+ inputs=inputs,
+ config=ExplainerConfig(
+ method_config=CaptumIntegratedGradientsConfig(n_steps=16),
+ backend=ExplainerBackendType.TORCH,
+ device="cpu",
+ ),
+ input_spec=ExplainerInputSpec(
+ feature_names=("mass", "velocity", "temperature"),
+ ),
+ output_spec=ExplainerOutputSpec(
+ task_type=ExplainerTaskType.REGRESSION,
+ output_names=("score",),
+ ),
+ target_spec=ExplainerTargetSpec(target_index=0),
+)
+
+result = ModelExplainer().explain(request)
+
+print(result.values.shape)
+print(result.method)
+```
+
+Expected output shape:
+
+```text
+torch.Size([2, 3])
+captum.integrated_gradients
+```
+
+## SHAP Tabular Explanation
+
+Use SHAP when you need black-box, tabular explanations. Torch models and Python
+callables are supported.
+
+```python
+import torch
+
+from pyTorchAutoForge.evaluation.explainability import (
+ ExplainerBackendType,
+ ExplainerConfig,
+ ExplainerInputSpec,
+ ExplainerOutputSpec,
+ ExplainerRequest,
+ ExplainerTaskType,
+ ModelExplainer,
+ ShapExplainerConfig,
+)
+
+model = torch.nn.Sequential(
+ torch.nn.Linear(3, 1),
+)
+
+inputs = torch.tensor(
+ [
+ [0.2, 0.4, 0.8],
+ [0.5, 0.1, 0.3],
+ [0.9, 0.6, 0.2],
+ ],
+ dtype=torch.float32,
+)
+
+request = ExplainerRequest(
+ model=model,
+ inputs=inputs,
+ background_data=inputs,
+ config=ExplainerConfig(
+ method_config=ShapExplainerConfig(max_background_samples=3),
+ backend=ExplainerBackendType.TORCH,
+ ),
+ input_spec=ExplainerInputSpec(
+ feature_names=("mass", "velocity", "temperature"),
+ ),
+ output_spec=ExplainerOutputSpec(
+ task_type=ExplainerTaskType.REGRESSION,
+ output_names=("score",),
+ ),
+)
+
+result = ModelExplainer().explain(request)
+
+print(result.values.shape)
+print(result.method)
+```
+
+Expected output:
+
+```text
+(3, 3)
+shap
+```
+
+## Saving Results
+
+Set `save_outputs=True` to write portable artifacts:
+
+```python
+request.config.save_outputs = True
+request.config.output_folder = "explainer_outputs"
+result = ModelExplainer().explain(request)
+
+print(result.metadata["saved_artifacts"])
+```
+
+Saved files:
+
+- `explanation.npz`: arrays such as values, data, predictions, and names.
+- `explanation.json`: method, backend, metadata, and raw object type names.
+
+## Public API
+
+- {class}`pyTorchAutoForge.evaluation.explainability.ModelExplainer`
+- {class}`pyTorchAutoForge.evaluation.explainability.ExplainerConfig`
+- {class}`pyTorchAutoForge.evaluation.explainability.ExplainerRequest`
+- {class}`pyTorchAutoForge.evaluation.explainability.ExplainerInputSpec`
+- {class}`pyTorchAutoForge.evaluation.explainability.ExplainerOutputSpec`
+- {class}`pyTorchAutoForge.evaluation.explainability.ExplainerTargetSpec`
+- {class}`pyTorchAutoForge.evaluation.explainability.ShapExplainerConfig`
+- {class}`pyTorchAutoForge.evaluation.explainability.CaptumIntegratedGradientsConfig`
+- {class}`pyTorchAutoForge.evaluation.explainability.CaptumSaliencyConfig`
+- {class}`pyTorchAutoForge.evaluation.explainability.CaptumGradientShapConfig`
+- {class}`pyTorchAutoForge.evaluation.explainability.ExplainerResult`
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 3dbe87f..6c35e66 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,6 +1,5 @@
sphinx
-sphinx_rtd_theme
-numpy
-pyyaml
-matplotlib
-torch
\ No newline at end of file
+pydata-sphinx-theme
+myst-parser
+sphinx-autoapi
+sphinx-copybutton
diff --git a/doc/roadmap.md b/doc/roadmap.md
new file mode 100644
index 0000000..c4e01b4
--- /dev/null
+++ b/doc/roadmap.md
@@ -0,0 +1,15 @@
+# Development Roadmap
+
+## Active Infrastructure Work
+
+- GitHub Pages documentation with Sphinx and the PyData theme.
+- Test markers for slow, GPU, visual, integration, and export paths.
+- Coverage reporting through `pytest-cov` and existing coverage config.
+- Better split between runnable examples and pytest modules.
+
+## Near-Term Test Cleanup
+
+- Replace external dataset tests with temp-fixture datasets where possible.
+- Keep expensive external-data tests under `slow` and `integration` markers.
+- Convert TCP server smoke checks to bounded ephemeral-port tests.
+- Add seeded edge-case checks for augmentation geometry and dataset selection.
diff --git a/doc/runtime_export.md b/doc/runtime_export.md
new file mode 100644
index 0000000..ab43834
--- /dev/null
+++ b/doc/runtime_export.md
@@ -0,0 +1,98 @@
+# Runtime And Export
+
+```{admonition} Codex-generated note
+:class: note
+
+This guide is partially generated by Codex and should be reviewed against the current source before treating examples as authoritative.
+```
+
+## When to use this module
+
+Use {mod}`pyTorchAutoForge.api` when models need to cross the boundary from Python training code into saved Torch artifacts, ONNX files, runtime sessions, MATLAB wrappers, or deployment backends. The stable path starts with Torch I/O and ONNX export; the generic runtime facade then delegates inference to ONNX Runtime or TensorRT-specific backends.
+
+Prefer this module when:
+
+- checkpoints should be saved with a consistent naming convention;
+- ONNX export should share path handling, validation flags, and backend selection;
+- inference code needs one facade that can switch between ONNX and TensorRT;
+- TensorRT is an optional deployment backend rather than a required local dependency.
+
+## Minimal example
+
+```python
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+import torch
+
+from pyTorchAutoForge.api.onnx import ModelHandlerONNx
+from pyTorchAutoForge.api.torch import AutoForgeModuleSaveMode, LoadModel, SaveModel
+
+model_ = torch.nn.Linear(2, 1).eval()
+example_input_ = torch.ones(1, 2)
+
+with TemporaryDirectory() as tmp_dir_:
+ model_base_ = Path(tmp_dir_) / "linear"
+ SaveModel(
+ model=model_,
+ model_filename=model_base_,
+ save_mode=AutoForgeModuleSaveMode.MODEL_STATE_DICT,
+ target_device=torch.device("cpu"),
+ )
+ loaded_model_ = LoadModel(
+ model=torch.nn.Linear(2, 1),
+ model_filename=str(model_base_) + "_statedict",
+ load_strict=True,
+ )
+
+ onnx_path_ = Path(tmp_dir_) / "linear.onnx"
+ handler_ = ModelHandlerONNx(
+ model=loaded_model_.eval(),
+ dummy_input_sample=example_input_,
+ onnx_export_path=str(onnx_path_),
+ opset_version=13,
+ run_export_validation=False,
+ generate_report=False,
+ run_onnx_simplify=False,
+ )
+ exported_path_ = handler_.export_onnx(backend="legacy")
+
+ print((model_base_.parent / "linear_statedict.pth").is_file())
+ print(Path(exported_path_).suffix)
+```
+
+Expected output, ignoring informational save/export logs:
+
+```text
+True
+.onnx
+```
+
+## Common workflow
+
+1. Save Torch models through `SaveModel` using a save mode that matches the downstream consumer.
+2. Export ONNX through `ModelHandlerONNx.export_onnx`, with validation enabled for release artifacts.
+3. Use `OnnxRuntimeApi` directly for ONNX-only inference or `ModelRuntimeApi` when backend dispatch should be configured at call time.
+4. Treat TensorRT export and runtime as optional: use it when local `tensorrt`, CUDA, and builder tooling are available.
+5. Keep ONNX input/output names and dynamic axes aligned with model call signatures before sending artifacts to external runtimes.
+
+## API links
+
+- {mod}`pyTorchAutoForge.api`
+- {mod}`pyTorchAutoForge.api.torch`
+- {mod}`pyTorchAutoForge.api.onnx`
+- {mod}`pyTorchAutoForge.api.runtime`
+- {mod}`pyTorchAutoForge.api.tensorrt`
+- {func}`pyTorchAutoForge.api.torch.torchModulesIO.SaveModel`
+- {func}`pyTorchAutoForge.api.torch.torchModulesIO.LoadModel`
+- {class}`pyTorchAutoForge.api.onnx.ModelHandlerONNx.ModelHandlerONNx`
+- {class}`pyTorchAutoForge.api.onnx.OnnxRuntimeApi.OnnxRuntimeApi`
+- {class}`pyTorchAutoForge.api.runtime.ModelRuntimeApi.ModelRuntimeApi`
+- {class}`pyTorchAutoForge.api.tensorrt.TRTengineExporter.TRTengineExporter`
+- {class}`pyTorchAutoForge.api.tensorrt.TensorrtRuntimeApi.TensorrtRuntimeApi`
+
+## Notes and limitations
+
+- TensorRT APIs require local CUDA/TensorRT installation and suitable engine-building tools. Keep TensorRT examples optional in portable docs.
+- ONNX export behavior depends on installed Torch and ONNX versions. For release artifacts, test both export and runtime inference with representative inputs.
+- TCP, MATLAB, and MLflow API surfaces are documented in the generated API reference, but they are not expanded into this runtime/export guide.
diff --git a/doc/sources/pyTorchAutoForge.datasets.rst b/doc/sources/pyTorchAutoForge.datasets.rst
deleted file mode 100644
index 7f2a0c0..0000000
--- a/doc/sources/pyTorchAutoForge.datasets.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-pyTorchAutoForge.datasets
-=========================
-
-.. automodule:: pyTorchAutoForge.datasets
-
-
-.. rubric:: Modules
-
-.. autosummary::
- :toctree:
- :recursive:
-
- DataAugmentation
- DataloaderIndex
- DatasetClasses
diff --git a/doc/sources/pyTorchAutoForge.evaluation.rst b/doc/sources/pyTorchAutoForge.evaluation.rst
deleted file mode 100644
index 8db47d2..0000000
--- a/doc/sources/pyTorchAutoForge.evaluation.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-pyTorchAutoForge.evaluation
-===========================
-
-.. automodule:: pyTorchAutoForge.evaluation
-
-
-.. rubric:: Modules
-
-.. autosummary::
- :toctree:
- :recursive:
-
- ModelEvaluator
- ModelProfiler
- ResultsPlotter
diff --git a/doc/sources/pyTorchAutoForge.hparams_optim.rst b/doc/sources/pyTorchAutoForge.hparams_optim.rst
deleted file mode 100644
index 0d3d6dd..0000000
--- a/doc/sources/pyTorchAutoForge.hparams_optim.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-pyTorchAutoForge.hparams\_optim
-===============================
-
-.. automodule:: pyTorchAutoForge.hparams_optim
-
-
-.. rubric:: Modules
-
-.. autosummary::
- :toctree:
- :recursive:
-
- ModelHparamsOptimizer
- OptunaStudyAnalyzer
- optuna_auxiliary
diff --git a/doc/sources/pyTorchAutoForge.optimization.rst b/doc/sources/pyTorchAutoForge.optimization.rst
deleted file mode 100644
index 20435ec..0000000
--- a/doc/sources/pyTorchAutoForge.optimization.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-pyTorchAutoForge.optimization
-=============================
-
-.. automodule:: pyTorchAutoForge.optimization
-
-
-.. rubric:: Modules
-
-.. autosummary::
- :toctree:
- :recursive:
-
- ModelTrainingManager
- lossFunctionsClasses
diff --git a/doc/sources/pyTorchAutoForge.setup.rst b/doc/sources/pyTorchAutoForge.setup.rst
deleted file mode 100644
index d939d91..0000000
--- a/doc/sources/pyTorchAutoForge.setup.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-pyTorchAutoForge.setup
-======================
-
-.. automodule:: pyTorchAutoForge.setup
-
-
-.. rubric:: Modules
-
-.. autosummary::
- :toctree:
- :recursive:
-
- AutoForgeInit
- BaseConfigClass
diff --git a/doc/sources/pyTorchAutoForge.utils.rst b/doc/sources/pyTorchAutoForge.utils.rst
deleted file mode 100644
index fcd8661..0000000
--- a/doc/sources/pyTorchAutoForge.utils.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-pyTorchAutoForge.utils
-======================
-
-.. automodule:: pyTorchAutoForge.utils
-
-
-.. rubric:: Modules
-
-.. autosummary::
- :toctree:
- :recursive:
-
- ArgsParser
- DeviceManager
- LossLandscapeVisualizer
- conversion_utils
- pytest_test
- timing_utils
- utils
diff --git a/doc/testing.md b/doc/testing.md
new file mode 100644
index 0000000..af94d3e
--- /dev/null
+++ b/doc/testing.md
@@ -0,0 +1,44 @@
+# Testing
+
+This page is for contributors and maintainers. It documents PTAF test commands,
+marker policy, and coverage checks; user-facing module workflows live in the
+User Guide.
+
+## Default Test Run
+
+```bash
+./run_tests.sh -- -q
+```
+
+`run_tests.sh` defaults to `autoforge`. Override when needed:
+
+```bash
+./run_tests.sh -e other_env -- -q
+```
+
+## Marker Policy
+
+Default collection skips tests marked `slow`, `gpu`, or `visual`.
+
+Use explicit flags when needed:
+
+```bash
+./run_tests.sh -- --run-slow -q
+./run_tests.sh -- --run-gpu -q
+./run_tests.sh -- --run-visual -q
+```
+
+Markers:
+
+- `unit`: fast local unit test.
+- `integration`: spans multiple subsystems or external-style behavior.
+- `slow`: intentionally excluded from default runs.
+- `gpu`: requires usable CUDA kernels, not just CUDA discovery.
+- `visual`: plotting or visual-inspection-oriented test.
+- `export`: model export or serialization path.
+
+## Coverage
+
+```bash
+./run_tests.sh -- --cov=pyTorchAutoForge --cov-report=term-missing:skip-covered
+```
diff --git a/doc/training_monitoring.md b/doc/training_monitoring.md
new file mode 100644
index 0000000..b12e444
--- /dev/null
+++ b/doc/training_monitoring.md
@@ -0,0 +1,116 @@
+# Training And Monitoring
+
+```{admonition} Codex-generated note
+:class: note
+
+This guide is partially generated by Codex and should be reviewed against the current source before treating examples as authoritative.
+```
+
+## When to use this module
+
+Use {mod}`pyTorchAutoForge.optimization` and {mod}`pyTorchAutoForge.monitoring` when training code needs PTAF configuration objects, dataloader plumbing, checkpoint controls, loss helpers, and run loggers. The training manager owns the train/validation loop, while monitoring loggers fan out run parameters and metrics to MLflow, TensorBoard-compatible event files, or custom logger objects.
+
+Prefer these modules when:
+
+- a model should train from a reusable `ModelTrainingManagerConfig`;
+- train/validation dataloaders are already grouped by `DataloaderIndex`;
+- logs should be written through one interface regardless of backend;
+- MLflow setup should be kept separate from model/training definitions.
+
+## Minimal example
+
+```python
+import torch
+from torch.utils.data import DataLoader, TensorDataset
+
+from pyTorchAutoForge.datasets import DataloaderIndex
+from pyTorchAutoForge.monitoring import CompositeRunLogger
+from pyTorchAutoForge.optimization import (
+ ModelTrainingManager,
+ ModelTrainingManagerConfig,
+ TaskType,
+)
+
+
+class SpyLogger:
+ def __init__(self) -> None:
+ self.epochs = []
+
+ def log_run_params(self, params_dict, **kwargs) -> None:
+ self.params = dict(params_dict)
+
+ def log_epoch_data(self, epoch_num, train_metrics, **kwargs) -> None:
+ self.epochs.append(epoch_num)
+
+
+features_ = torch.randn(8, 3)
+targets_ = torch.randn(8, 1)
+train_loader_ = DataLoader(TensorDataset(features_[:6], targets_[:6]), batch_size=2)
+valid_loader_ = DataLoader(TensorDataset(features_[6:], targets_[6:]), batch_size=2)
+loader_index_ = DataloaderIndex(train_loader_, valid_loader_)
+
+model_ = torch.nn.Linear(3, 1)
+optimizer_ = torch.optim.SGD(model_.parameters(), lr=1e-2)
+config_ = ModelTrainingManagerConfig(
+ tasktype=TaskType.REGRESSION,
+ batch_size=2,
+ num_of_epochs=1,
+ initial_lr=1e-2,
+ optimizer=optimizer_,
+ mlflow_logging=False,
+ label_scaling_factors=torch.ones(1),
+ device="cpu",
+)
+trainer_ = ModelTrainingManager(
+ model=model_,
+ lossFcn=torch.nn.MSELoss(),
+ config=config_,
+ dataLoaderIndex=loader_index_,
+)
+
+spy_logger_ = SpyLogger()
+logger_ = CompositeRunLogger(spy_logger_)
+logger_.log_run_params({"batch_size": trainer_.batch_size})
+logger_.log_epoch_data(epoch_num=trainer_.current_epoch, train_metrics={"loss": 0.0})
+
+print(trainer_.num_of_epochs)
+print(trainer_.trainingDataloader.batch_size)
+print(spy_logger_.epochs)
+```
+
+Expected output, ignoring trainer setup logs:
+
+```text
+1
+2
+[0]
+```
+
+## Common workflow
+
+1. Build Torch datasets and collect loaders in `DataloaderIndex`.
+2. Define model, loss, optimizer, scheduler, and `ModelTrainingManagerConfig`.
+3. Instantiate `ModelTrainingManager`, attach dataloaders, and run `trainAndValidate()` for real training jobs.
+4. Use `CompositeRunLogger` when multiple monitoring backends should receive the same metrics.
+5. Configure MLflow explicitly through {mod}`pyTorchAutoForge.api.mlflow` helpers before enabling MLflow logging.
+
+## API links
+
+- {mod}`pyTorchAutoForge.optimization`
+- {mod}`pyTorchAutoForge.monitoring`
+- {mod}`pyTorchAutoForge.api.mlflow`
+- {mod}`pyTorchAutoForge.hparams_optim`
+- {class}`pyTorchAutoForge.optimization.ModelTrainingManager.ModelTrainingManager`
+- {class}`pyTorchAutoForge.optimization.ModelTrainingManager.ModelTrainingManagerConfig`
+- {class}`pyTorchAutoForge.optimization.ModelTrainingManager.TaskType`
+- {func}`pyTorchAutoForge.optimization.ModelTrainingManager.FreezeModel`
+- {class}`pyTorchAutoForge.monitoring.run_loggers.CompositeRunLogger`
+- {class}`pyTorchAutoForge.monitoring.run_loggers.MlflowRunLogger`
+- {class}`pyTorchAutoForge.monitoring.run_loggers.TensorBoardRunLogger`
+- {func}`pyTorchAutoForge.api.mlflow.mlflow_api.SetupMlflowTrackingSession`
+
+## Notes and limitations
+
+- Training manager examples here stop before a full optimization loop to keep docs lightweight. Use tests and project examples for full epoch-level behavior.
+- MLflow and TensorBoard logging depend on optional packages and local filesystem/server setup.
+- Hyperparameter and Optuna utilities remain API-linked for now; they are not promoted to a task guide here.
diff --git a/doc/utilities.md b/doc/utilities.md
new file mode 100644
index 0000000..a4eab3a
--- /dev/null
+++ b/doc/utilities.md
@@ -0,0 +1,83 @@
+# Utilities
+
+```{admonition} Codex-generated note
+:class: note
+
+This guide is partially generated by Codex and should be reviewed against the current source before treating examples as authoritative.
+```
+
+## When to use this module
+
+Use {mod}`pyTorchAutoForge.utils` for small cross-cutting helpers that support model code, dataset code, timing checks, device selection, conversion, and argument parsing. These helpers are useful around the core workflows, but they should not hide task-specific logic.
+
+Prefer this module when:
+
+- tensors and arrays need consistent conversion at API boundaries;
+- quick timing checks should average repeated calls;
+- model parameter size or trainable parameter counts are needed;
+- a device selector should centralize CPU/CUDA/MPS decisions.
+
+## Minimal example
+
+```python
+import numpy as np
+import torch
+
+from pyTorchAutoForge.utils import (
+ AddZerosPadding,
+ ComputeModelParamsStorageSize,
+ numpy_to_torch,
+ timeit_averaged_,
+ torch_to_numpy,
+)
+
+array_ = np.array([1.0, 2.0], dtype=np.float32)
+tensor_ = numpy_to_torch(array_)
+doubled_ = torch_to_numpy(tensor_ * 2.0)
+
+model_ = torch.nn.Linear(2, 1)
+elapsed_sec_ = timeit_averaged_(lambda value_: value_.sum().item(), 2, tensor_)
+
+print(doubled_.tolist())
+print(AddZerosPadding(7, "3"))
+print(ComputeModelParamsStorageSize(model_) > 0.0)
+print(elapsed_sec_ >= 0.0)
+```
+
+Expected output:
+
+```text
+[2.0, 4.0]
+007
+True
+True
+```
+
+## Common workflow
+
+1. Use conversion helpers at module boundaries instead of duplicating tensor/NumPy conversion logic.
+2. Use device helpers when user code should choose CPU, CUDA, MPS, or multi-device behavior consistently.
+3. Wrap expensive smoke checks with timing helpers when comparing candidate implementations.
+4. Keep context-management helpers close to operations that may block or time out.
+5. Promote repeated task logic into a domain module once it becomes more than a general utility.
+
+## API links
+
+- {mod}`pyTorchAutoForge.utils`
+- {mod}`pyTorchAutoForge.utils.DeviceManager`
+- {mod}`pyTorchAutoForge.utils.conversion_utils`
+- {mod}`pyTorchAutoForge.utils.timing_utils`
+- {mod}`pyTorchAutoForge.utils.context_management`
+- {mod}`pyTorchAutoForge.utils.argument_parsers`
+- {func}`pyTorchAutoForge.utils.conversion_utils.torch_to_numpy`
+- {func}`pyTorchAutoForge.utils.conversion_utils.numpy_to_torch`
+- {func}`pyTorchAutoForge.utils.timing_utils.timeit_averaged`
+- {func}`pyTorchAutoForge.utils.timing_utils.timeit_averaged_`
+- {class}`pyTorchAutoForge.utils.DeviceManager.DeviceManager`
+- {class}`pyTorchAutoForge.utils.context_management.TimeoutException`
+
+## Notes and limitations
+
+- Some utilities predate current naming and type-hint conventions. Prefer stable exported helpers over test fixtures or one-off scripts.
+- `GetDevice()` is a legacy simple selector; use `GetDeviceMulti()` or `DeviceManager` when richer hardware choice matters.
+- Utilities should remain thin. If a helper starts owning dataset, model, or runtime semantics, move that logic to the owning module.
diff --git a/environment-ci.yml b/environment-ci.yml
index 2cd4f5c..44bd627 100644
--- a/environment-ci.yml
+++ b/environment-ci.yml
@@ -3,9 +3,11 @@ channels:
- conda-forge
- defaults
dependencies:
- - python=3.11
+ - python=3.12
- flake8
- pip
- setuptools
- wheel
- pytest
+ - coverage
+ - pytest-cov
diff --git a/install_all.sh b/install_all.sh
deleted file mode 100755
index a8131bf..0000000
--- a/install_all.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-echo "Hello! Installation script begins. This is going to install pytorch-autoforge with all the dependencies in a virtual env and build its documentation."
-echo -e "Starting in 1 second...\n\n"
-sleep 1
-sudo apt install python3.11 python3.11-venv # Install python3-venv
-python3.11 -m venv .venvTorch # Create virtual environment
-source .venvTorch/bin/activate # Activate virtual environment
-pip install -e . --require-virtualenv # Install the package
-#pip install -e -r requirements.txt --require-virtualenv # Install dependencies
-
-# Install sphinx and theme, and build the documentation
-pip install sphinx sphinx_rtd_theme --require-virtualenv
-make -C docs html
\ No newline at end of file
diff --git a/install_submodule.sh b/install_submodule.sh
deleted file mode 100755
index b8979c5..0000000
--- a/install_submodule.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
-cd $SCRIPT_DIR
-
-echo "Hello! Installation script begins. This is going to install pytorch-autoforge with all the dependencies in a virtual env and build its documentation."
-echo -e "Starting in 1 second...\n\n"
-sleep 1
-sudo apt install python3.11 python3.11-venv # Install python3-venv
-
-cd ../.. # Go to the root directory of the project
-python3.11 -m venv .venvTorch # Create virtual environment
-source .venvTorch/bin/activate # Activate virtual environment
-pip install -r $SCRIPT_DIR/requirements.txt --require-virtualenv # Install dependencies
-pip install $SCRIPT_DIR --require-virtualenv -e # Install the package in editable mode
-cd $SCRIPT_DIR
-
-# Install sphinx and theme, and build the documentation
-pip install sphinx sphinx_rtd_theme --require-virtualenv
-make -C docs html
\ No newline at end of file
diff --git a/install_with_venv.sh b/install_with_venv.sh
deleted file mode 100755
index ea36972..0000000
--- a/install_with_venv.sh
+++ /dev/null
@@ -1,202 +0,0 @@
-# Default values
-jetson_target=0
-editable_mode=1
-sudo_mode=0
-venv_name=".venvTorch"
-
-# Parse options using getopt
-# NOTE: no ":" after option means no argument, ":" means required argument, "::" means optional argument
-OPTIONS=j,v:,s
-LONGOPTIONS=jetson_target,venv_name:,sudo_mode
-
-# Parsed arguments list with getopt
-PARSED=$(getopt --options ${OPTIONS} --longoptions ${LONGOPTIONS} --name "$0" -- "$@")
-# TODO check if this is where I need to modify something to allow things like -B build, instead of -Bbuild
-
-# Check validity of input arguments
-if [[ $? -ne 0 ]]; then
- exit 2
-fi
-
-# Parse arguments
-eval set -- "$PARSED"
-
-# Process options (change default values if needed)
-while true; do
- case "$1" in
- -j|--jetson_target)
- jetson_target=1
- echo "Jetson target selected..."
- shift
- ;;
- -v|--venv_name)
- venv_name=$2
- echo "Virtual environment name: $venv_name"
- shift 2
- ;;
- -s|--sudo_mode)
- sudo_mode=1
- echo "Sudo mode requested..."
- shift
- ;;
- --)
- shift
- break
- ;;
- *)
- echo "Not a valid option: $1" >&2
- exit 3
- ;;
- esac
-done
-
-if [ $jetson_target -eq 1 ] && [ ! $sudo_mode -eq 1 ]; then
- echo "Jetson target requires sudo mode. Please use -s option."
- exit 1
-fi
-
-if [ $sudo_mode -eq 1 ]; then
- echo "Running in sudo mode..."
- sudo apt install python3.11 python3.11-venv # Install python3-venv
-fi
-
-
-if [ $jetson_target -eq 1 ] && [ ! -f /usr/local/cuda/lib64/libcusparseLt.so ]; then
- echo "libcusparseLt.so not found. Downloading and installing..."
- # if not exist, download and copy to the directory
- wget https://developer.download.nvidia.com/compute/cusparselt/redist/libcusparse_lt/linux-sbsa/libcusparse_lt-linux-sbsa-0.5.2.1-archive.tar.xz
- tar xf libcusparse_lt-linux-sbsa-0.5.2.1-archive.tar.xz
- sudo cp -a libcusparse_lt-linux-sbsa-0.5.2.1-archive/include/* /usr/local/cuda/include/
- sudo cp -a libcusparse_lt-linux-sbsa-0.5.2.1-archive/lib/* /usr/local/cuda/lib64/
- rm libcusparse_lt-linux-sbsa-0.5.2.1-archive.tar.xz
- rm -r libcusparse_lt-linux-sbsa-0.5.2.1-archive
-fi
-
-if [ $jetson_target -eq 1 ]; then
-
- # Create virtualenv for Jetson
- python3 -m venv $venv_name --system-site-packages # Create virtual environment
- source $venv_name/bin/activate # Activate virtual environment
-
- #pip install -r requirements.txt --require-virtualenv # Install dependencies
- #pip install -e . --require-virtualenv # Install the package in editable mode
-
- # Tools for building and installing wheels
- echo "Installing setuptools, twine, and build..."
- pip install setuptools twine build --require-virtualenv
- python3 -m ensurepip --upgrade --require-virtualenv
- python3 -m pip install --upgrade pip --require-virtualenv
-
- # Install key modules not managed by dependencies installation for versioning reasons
- echo "Installing additional key modules..."
-
- # Remove torch and torchvision
- pip uninstall -y torch torchvision torchaudio
-
- # Install torch for Jetson
- pip install torch https://developer.download.nvidia.com/compute/redist/jp/v61/pytorch/torch-2.5.0a0+872d972e41.nv24.08.17622132-cp310-cp310-linux_aarch64.whl --require-virtualenv
-
- # Build and install torchvision from source
- # From guide: https://github.com/azimjaan21/jetpack-6.1-pytorch-torchvision-/blob/main/README.md
- git clone https://github.com/pytorch/vision.git
- cd vision
- git checkout tags/v0.20.0
- python3 setup.py install
-
- # Clean up
- cd ..
- sudo rm -r vision
-
- # Try to build torch-tensorrt
- source $venv_name/bin/activate # Activate virtual environment
-
- #pip install norse==1.0.0 aestream tonic expelliarmus --ignore-requires-python3 --require-virtualenv # FIXME: build fails due to "CUDA20" entry
-
- pip install nvidia-pyindex pycuda --require-virtualenv
-
- # ACHTUNG: this must run correctly before torch_tensorrt
- pip install "nvidia-modelopt[all]" -U --extra-index-url https://pypi.nvidia.com
-
- # Install torch-tensorrt from source
- mkdir lib
- cd lib
-
- # Check if submodule exists
- if [ -d "TensorRT" ]; then
- echo "TensorRT submodule exists"
- else
- git submodule add --branch release/2.5 https://github.com/pytorch/TensorRT.git # Try to use release/2.6 (latest)
- fi
-
- cd TensorRT
- git checkout release/2.5
- git pull
-
- # Install required python3 packages of torch-tensorrt
- python3 -m pip install -r toolchains/jp_workspaces/requirements.txt # NOTE: Installs the correct version of setuptools. Do not touch it.
-
- cuda_version=$(nvcc --version | grep Cuda | grep release | cut -d ',' -f 2 | sed -e 's/ release //g')
- export TORCH_INSTALL_PATH=$(python3 -c "import torch, os; print(os.path.dirname(torch.__file__))")
- export SITE_PACKAGE_PATH=${TORCH_INSTALL_PATH::-6}
- export CUDA_HOME=/usr/local/cuda-${cuda_version}/
-
- # Replace the MODULE.bazel with the jetpack one # DOUBT: why needed?
- cat toolchains/jp_workspaces/MODULE.bazel.tmpl | envsubst > MODULE.bazel
-
- # Build and install torch_tensorrt wheel file with CXX11 ABI
- python3 setup.py install --use-cxx11-abi
- cd ../..
-
- # Finally, build pyTorchAutoForge wheel
- if [ "$editable_mode" = true ]; then
- echo "Building and installing pyTorchAutoForge in editable mode..."
- pip install -e . --require-virtualenv # Install the package in editable mode
- else
- echo "Building and installing pyTorchAutoForge wheel..."
- python3 -m build
- pip install -e dist/*.whl --require-virtualenv # Install pyTorchAutoForge wheel
- fi
-
-else
-
- # Create virtualenv for other targets
- python3 -m venv $venv_name # Create virtual environment
- source $venv_name/bin/activate # Activate virtual environment
-
- #pip install -r requirements.txt --require-virtualenv # Install dependencies that do not cause issues...
- #python3 -m pip install -r toolchains/jp_workspaces/test_requirements.txt # Required for test cases
-
- # Tools for building and installing wheels
- echo "Installing setuptools, twine, and build..."
- pip install setuptools twine build --require-virtualenv
- python3 -m ensurepip --upgrade --require-virtualenv
- python3 -m pip install --upgrade pip --require-virtualenv
-
- # Install key modules not managed by dependencies installation for versioning reasons
- echo "Installing additional key modules..."
- pip install norse tonic aestream expelliarmus --require-virtualenv
-
- pip install pynvml --require-virtualenv # Install pynvml for GPU monitoring
-
- # Build pyTorchAutoForge wheel
- if [ "$editable_mode" = true ]; then
- echo "Building and installing pyTorchAutoForge in editable mode..."
- pip install -e . --require-virtualenv # Install the package in editable mode
- else
- echo "Building and installing pyTorchAutoForge wheel..."
- python3 -m build
- pip install -e dist/*.whl --require-virtualenv # Install pyTorchAutoForge wheel
- fi
-
- # Install tools for model optimization and deployment
- echo "Installing tools for model optimization and deployment by Nvidia..."
- python3 -m pip install pycuda torch torchvision torch-tensorrt tensorrt "nvidia-modelopt[all]" -U --extra-index-url https://pypi.nvidia.com
- fi
-
- deactivate # Deactivate virtual environment if any
- source $venv_name/bin/activate # Activate virtual environment
- # Check installation by printing versions in python3
- python3 -m tests/.configuration/test_env
-
-
-
diff --git a/pyproject.toml b/pyproject.toml
index 0479bc0..a747d9b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "pyTorchAutoForge"
-description='PyTorchAutoForge library is based on raw PyTorch and designed to automate DNN development, model tracking and deployment, tightly integrated with MLflow and Optuna. It supports Spiking networks libraries (WIP). Deployment can be performed using ONNX, pyTorch facilities or TensorRT (WIP). The library is designed to be compatible with Jetson Orin Nano Jetpack rev6.1, with bash script to automatically configure virtualenv.'
+description='PyTorchAutoForge library is based on raw PyTorch and designed to automate DNN development, model tracking and deployment, tightly integrated with MLflow and Optuna. It supports Spiking networks libraries (WIP). Deployment can be performed using ONNX, pyTorch facilities or TensorRT (WIP). The library is designed to be compatible with Jetson Orin Nano Jetpack rev6.1, with a bash script to configure conda-based development environments.'
readme = "README.md"
requires-python = ">=3.10"
dynamic = ["version"]
@@ -16,7 +16,6 @@ dependencies = [
"torch-tb-profiler",
"scikit-learn<=1.6.1",
"dacite",
- "captum",
"scipy",
"numpy",
"onnx",
@@ -34,7 +33,7 @@ dependencies = [
"colorama",
"msgpack",
"torchinfo",
- "torch>=2.7,<2.10.0; platform_machine == 'x86_64'", # Only for x86_64. Jetson requires nvidia version
+ "torch>=2.7,<2.12; platform_machine == 'x86_64'", # Only for x86_64. Jetson requires nvidia version
"torchvision; platform_machine == 'x86_64'", # Only for x86_64. Jetson requires nvidia version
"norse; platform_machine == 'x86_64'",
"tonic; platform_machine == 'x86_64'",
@@ -57,6 +56,17 @@ classifiers=[
cuda_all = [
"pynvml; platform_machine == 'x86_64'",
]
+test = [
+ "coverage[toml]",
+ "pytest-cov",
+]
+docs = [
+ "sphinx",
+ "pydata-sphinx-theme",
+ "myst-parser",
+ "sphinx-autoapi",
+ "sphinx-copybutton",
+]
[tool.hatch.build.targets.wheel]
packages = ["pyTorchAutoForge"]
@@ -72,6 +82,14 @@ addopts = """--ignore=lib/
--disable-warnings
"""
console_output_style = "count"
+markers = [
+ "unit: fast test with no external service, large dataset, GPU, or long-running export requirement",
+ "integration: test spanning multiple subsystems or external-style runtime behavior",
+ "slow: test intentionally excluded from default runs unless --run-slow is passed",
+ "gpu: test requiring a usable CUDA runtime, excluded unless --run-gpu is passed",
+ "visual: plotting or visual-inspection-oriented test, excluded unless --run-visual is passed",
+ "export: model export or runtime-serialization test",
+]
# Specify folders to ignore
norecursedirs = ["*deprecated*", "*data", "*cache", "**/site-packages", "dist", "build"]
diff --git a/release_to_pypi.sh b/release_to_pypi.sh
index 899d9f0..9e1c8d1 100755
--- a/release_to_pypi.sh
+++ b/release_to_pypi.sh
@@ -1,21 +1,101 @@
#!/bin/bash
-source ~/miniconda3/etc/profile.d/conda.sh
-conda activate autoforge
+set -euo pipefail
-# Clear dist folder
-if [ -d "dist" ]; then
- rm -r dist
+# Script variables
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="${SCRIPT_DIR}"
+
+CONDA_EXE="${CONDA_EXE:-conda}"
+ENV_NAME="${CONDA_ENV:-autoforge}"
+REPOSITORY="pypi"
+DIST_DIR="dist"
+CLEAN=1
+UPLOAD=1
+
+# Usage guide
+usage() {
+ cat <<'EOF'
+Usage: ./release_to_pypi.sh [options]
+
+Builds package artifacts, runs twine check, then uploads to PyPI by default.
+
+Options:
+ -e, --env-name NAME Conda environment name (default: CONDA_ENV or autoforge)
+ --conda-exe PATH Conda executable (default: conda)
+ -r, --repository NAME Twine repository name (default: pypi)
+ --dist-dir PATH Distribution output directory (default: dist)
+ --no-clean Keep existing dist and egg-info folders
+ --skip-upload Build and check only
+ -h, --help Show this help
+EOF
+}
+
+# Parser loop
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -e|--env-name)
+ ENV_NAME="$2"
+ shift 2
+ ;;
+ --conda-exe)
+ CONDA_EXE="$2"
+ shift 2
+ ;;
+ -r|--repository)
+ REPOSITORY="$2"
+ shift 2
+ ;;
+ --dist-dir)
+ DIST_DIR="$2"
+ shift 2
+ ;;
+ --no-clean)
+ CLEAN=0
+ shift
+ ;;
+ --skip-upload)
+ UPLOAD=0
+ shift
+ ;;
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ *)
+ echo "Unknown option: $1" >&2
+ usage >&2
+ exit 2
+ ;;
+ esac
+done
+
+if ! command -v "${CONDA_EXE}" >/dev/null 2>&1; then
+ echo "Conda executable not found: ${CONDA_EXE}" >&2
+ exit 1
fi
-# Remove egg-info folder
-if [ -d "*.egg-info" ]; then
- rm -r *.egg-info
+# Source conda environment
+CONDA_BASE="$("${CONDA_EXE}" info --base)"
+# shellcheck source=/dev/null
+source "${CONDA_BASE}/etc/profile.d/conda.sh"
+conda activate "${ENV_NAME}"
+
+cd "${REPO_ROOT}"
+
+# Clean dist and egg-info if requested (default: yes)
+if [[ "${CLEAN}" -eq 1 ]]; then
+ rm -rf "${DIST_DIR}"
+ find . -maxdepth 1 -type d -name "*.egg-info" -exec rm -rf {} +
fi
-# Build latest wheel
-python -m build
+# Build and check artifacts
+python -m pip install --upgrade build twine
+python -m build --outdir "${DIST_DIR}"
+python -m twine check "${DIST_DIR}"/*
-if [ -d "dist" ]; then
- twine check dist/*
- twine upload --repository pypi dist/* #-u __token__ -p $PYPI_TOKEN --verbose
-fi
\ No newline at end of file
+# Upload to PyPI if requested (default: yes)
+if [[ "${UPLOAD}" -eq 1 ]]; then
+ python -m twine upload --repository "${REPOSITORY}" "${DIST_DIR}"/*
+else
+ echo "Upload skipped. Artifacts are in ${DIST_DIR}."
+fi
diff --git a/requirements.txt b/requirements.txt
index 6637d2d..1b2db59 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -245,15 +245,6 @@ smmap<=5.0.1
sniffio<=1.3.1
snowballstemmer<=2.2.0
soupsieve<=2.5
-Sphinx<=7.4.7
-sphinx-rtd-theme<=2.0.0
-sphinxcontrib-applehelp<=2.0.0
-sphinxcontrib-devhelp<=2.0.0
-sphinxcontrib-htmlhelp<=2.1.0
-sphinxcontrib-jquery<=4.1
-sphinxcontrib-jsmath<=1.0.1
-sphinxcontrib-qthelp<=2.0.0
-sphinxcontrib-serializinghtml<=2.0.0
SQLAlchemy<=2.0.35
sqlparse<=0.5.1
sympy<=1.13.2
@@ -293,4 +284,4 @@ wrapt<=1.16.0
xdg<=5
#xkit<=0.0.0
yacs<=0.1.8
-zipp<=3.20.2
\ No newline at end of file
+zipp<=3.20.2