diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d5f8bfb..04016f3 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2,46 +2,64 @@ applyTo: "**.typ" --- -# GitHub Copilot Instructions for Template +# GitHub Copilot Instructions for Academic Notes ## Project Context -This repository contains academic notes and materials for the Template course, written in Typst format. +This repository contains academic course notes written in Typst, using the [typst-notes-template](https://github.com/Favo02/typst-notes-template). +All custom functions and packages are made available via `#import "template.typ": *` (or a similar relative import) in each file. ## Review Focus Areas ### 1. Typst Syntax - You are not up to date with the Typst compiler, so if you find anything weird or uncommon, triple check before suggesting an edit -- The project uses specific external packages that are already imported: `gentle-clues`, `equate`, `codly`, `fletcher`, `cetz`, and `cetz-venn` -- Do NOT suggest importing NEW Typst external packages beyond those already in use +- All external packages (`gentle-clues`, `equate`, `codly`, `fletcher`, `cetz`, `cetz-venn`, `lovelace`, `pinit`) are already available through the template import - do NOT suggest importing them directly in note files +- Do NOT suggest importing NEW Typst external packages beyond those already provided by the template - It is guaranteed that the code compiles into a valid PDF document ### 2. Template Consistency -- Ensure consistent use of custom helper functions: `nota`, `attenzione`, `informalmente`, `dimostrazione`, `teorema`, and `esempio` -- Check proper application of the document template and styling +- Ensure consistent use of custom info box functions (English names): `note`, `warning`, `informally`, `example`, `proof`, and `theorem` + - All accept an optional `title:` named argument to override the default title +- Check proper application of the `academic-notes` template function and its parameters +- The template supports bilingual output via `lang: "en"` or `lang: "it"` in `academic-notes`; individual label overrides are available as optional named parameters - Check proper use of inline math `$...$` and display math `$ ... $` -- Ensure consistent use of colored math helpers: `mg` (olive), `mm` (maroon), `mo` (orange), `mr` (red), `mp` (purple), `mb` (blue) +- Ensure consistent use of colored math helpers: `mg` (green), `mm` (maroon), `mo` (orange), `mr` (red), `mp` (purple), `mb` (blue) +- Also check use of `comment()` for small inline comments inside equations - Verify proper use of diagram creation tools (`fletcher`, `cetz`, `cetz-venn`) for visual elements -- Check consistent formatting of definition lists using `/` notation -- Check that TODO comments are properly formatted and tracked +- Check consistent formatting of definition lists using `/` notation (`:` is the separator set by the template) +- Check that incomplete sections are marked with `#todo` (not any other pattern) +- Verify proper use of cross-reference helpers: `link-section()`, `link-theorem()`, `link-equation()` +- Check proper use of `part()` for major document divisions (numbered with Roman numerals) +- Verify pseudocode is written using `pseudocode()` and `indent()` from the `lovelace` package ### 3. Content Clarity -- Italian Language: Check for proper Italian grammar and academic writing style +- Language: Check for proper grammar and academic writing style matching the `lang` setting (`"en"` or `"it"`) - Explanations: Ensure complex concepts are explained clearly and progressively - Examples: Verify that examples effectively illustrate the concepts - Mathematical Accuracy: Verify mathematical formulas, algorithms, and proofs for correctness - Algorithmic Descriptions: Check that algorithm descriptions are clear, complete, and accurate - Complexity Analysis: Ensure Big O, Omega, and Theta notations are used correctly -- Terminology: Verify proper use of computer science and algorithmic terminology in Italian +- Terminology: Verify proper use of computer science and algorithmic terminology in the document's language -### 4. Code Examples and Pseudocode +### 4. Diagrams and Drawings + +Choose the right abstraction level for each drawing task - neither always high-level nor always low-level: + +- Prefer package-level abstractions when they match the concept: use `fletcher` nodes and edges for graphs, automata, and flowcharts; use `cetz-venn` for Venn diagrams. These map naturally to the concept and produce cleaner code. +- Fall back to `cetz` primitives for custom or geometric drawings: lines, rectangles, circles, arcs, and labels via `cetz.draw` are reliable and well-understood - use them for diagrams that don't fit a higher-level abstraction (e.g., geometric proofs, custom data structure illustrations). +- Avoid reinventing what packages already provide: do not draw a graph by manually placing circles and lines with `cetz` if `fletcher` can express the same thing with nodes and edges. +- Avoid over-relying on high-level API details you are uncertain about: `fletcher` and `cetz-venn` have specific APIs that may be mis-recalled - when in doubt, fall back to `cetz` primitives rather than guessing high-level parameters. +- Mixed use is fine: it is acceptable to combine `fletcher` for the graph structure and `cetz` for additional annotations in the same diagram. + +### 5. Code Examples and Pseudocode - Review algorithm implementations for correctness - Check that code examples follow good programming practices -- Verify syntax highlighting and code formatting using codly +- Verify syntax highlighting and code formatting using `codly` +- Ensure pseudocode uses `pseudocode()` and `indent()` from `lovelace`, not ad-hoc formatting - Ensure code examples match the theoretical explanations ## Specific Review Guidelines @@ -53,30 +71,32 @@ This repository contains academic notes and materials for the Template course, w 3. Suggest improvements for clarity and educational value 4. Check for consistency with existing content style 5. Do not suggest importing new Typst packages or features -6. Pay attention to TODO comments and incomplete sections marked by the `appunti` separator +6. Pay attention to `#todo` markers indicating incomplete or unverified sections ### Content Review Priorities: 1. **Mathematical Accuracy**: Verify all formulas, proofs, and complexity analysis 2. **Educational Flow**: Ensure concepts build logically and examples support theory -3. **Italian Grammar**: Check for proper academic Italian language usage +3. **Language Grammar**: Check for proper academic language usage matching the `lang` setting 4. **Template Consistency**: Verify proper use of all custom helper functions and styling 5. **Visual Elements**: Check that diagrams and mathematical formatting enhance understanding ### Common Patterns to Watch: - Proper use of `$` for inline math and `$ ... $` for display math -- Consistent use of colored math helpers: `mg()`, `mm()`, `mo()`, `mr()`, `mp()`, `mb()` -- Correct application of info boxes: `nota()`, `attenzione()`, `informalmente()`, `esempio()`, `dimostrazione()`, `teorema()` -- Proper Italian academic terminology for computer science concepts +- Consistent use of colored math helpers: `mg()`, `mm()`, `mo()`, `mr()`, `mp()`, `mb()`, `comment()` +- Correct application of info boxes: `note()`, `warning()`, `informally()`, `example()`, `proof()`, `theorem()` - Consistent use of definition lists with `/` separator for terminology sections -- Proper formatting of algorithms and pseudocode using `codly` -- Appropriate use of diagram tools (`fletcher`, `cetz`, `cetz-venn`) for visual representations +- Proper formatting of pseudocode using `lovelace`: `pseudocode()` and `indent()` +- Proper formatting of highlighted code using `codly` +- Appropriate use of diagram tools: `fletcher` for graphs/automata/flowcharts, `cetz-venn` for Venn diagrams, `cetz` primitives for custom geometric drawings - balanced between high-level APIs and low-level directives (see section 4) +- Cross-references using `link-section()`, `link-theorem()`, `link-equation()` +- Use of `#todo` to flag incomplete sections ## Interaction Style - Suggest improvements for complex errors - Simply point out minor issues (like typos) without extensive explanations -- Consider the academic context and Italian language requirements -- When reviewing TODO sections, provide constructive guidance on completion +- Consider the academic context and the document's language requirements +- When reviewing `#todo` sections, provide constructive guidance on completion - Respect the established template structure and don't suggest major architectural changes diff --git a/.github/workflows/pr-pdf-comment.yml b/.github/workflows/pr-pdf-comment.yml index 55bba04..e33fce6 100644 --- a/.github/workflows/pr-pdf-comment.yml +++ b/.github/workflows/pr-pdf-comment.yml @@ -12,56 +12,79 @@ jobs: permissions: contents: read pull-requests: write + actions: read steps: - - name: Announce PDF artifact on PR - uses: actions/github-script@v7 - with: - script: | - const run = context.payload.workflow_run - const pr = run.pull_requests && run.pull_requests[0] - - if (!pr) { - core.warning('No pull request associated with the workflow run; skipping comment.') - return - } - - const owner = context.repo.owner - const repo = context.repo.repo - const runId = run.id - const workflowUrl = run.html_url || `${context.payload.repository.html_url}/actions/runs/${runId}` - const expectedName = `pr-${pr.number}-preview` - - const artifactsResponse = await github.rest.actions.listWorkflowRunArtifacts({ - owner, - repo, - run_id: runId, - }) - - const artifact = artifactsResponse.data.artifacts.find(a => a.name === expectedName) - - if (!artifact) { - core.warning(`Artifact ${expectedName} not found on run ${runId}; skipping comment.`) - return - } - - const body = ` - 📄 **Anteprima PDF disponibile** - - L'ultima compilazione è stata eseguita sul commit \`${run.head_sha.substring(0, 7)}\`. - - **📥 Per scaricare il PDF:** - 1. [Apri il workflow run](${workflowUrl}) - 2. Vai alla sezione **Artifacts** - 3. Scarica \`${expectedName}\` - - L'artifact resta disponibile per 30 giorni. - ` - - await github.rest.issues.createComment({ - owner, - repo, - issue_number: pr.number, - body, - }) - core.info(`Created new comment on PR #${pr.number}`) + - name: Announce PDF artifact on PR + uses: actions/github-script@v7 + with: + script: | + const run = context.payload.workflow_run + const pr = run.pull_requests && run.pull_requests[0] + + if (!pr) { + core.warning('No pull request associated with the workflow run; skipping comment.') + return + } + + const owner = context.repo.owner + const repo = context.repo.repo + const runId = run.id + const shortSha = run.head_sha.substring(0, 7) + const workflowUrl = run.html_url || `${context.payload.repository.html_url}/actions/runs/${runId}` + const expectedName = `pr-${pr.number}-preview` + const marker = `` + + const artifactsResponse = await github.rest.actions.listWorkflowRunArtifacts({ + owner, + repo, + run_id: runId, + }) + + const artifact = artifactsResponse.data.artifacts.find(a => a.name === expectedName) + + if (!artifact) { + core.warning(`Artifact ${expectedName} not found on run ${runId}; skipping comment.`) + return + } + + const body = `${marker} + 📄 **PDF Preview available** + + Latest build ran on commit \`${shortSha}\`. + + **📥 To download the PDF:** + 1. [Open the workflow run](${workflowUrl}) + 2. Go to the **Artifacts** section + 3. Download \`${expectedName}\` + + The artifact is available for 30 days.` + + const existingComments = await github.rest.issues.listComments({ + owner, + repo, + issue_number: pr.number, + }) + + const existing = existingComments.data.find(c => c.body && c.body.includes(marker)) + + if (existing) { + try { + await github.rest.issues.deleteComment({ + owner, + repo, + comment_id: existing.id, + }) + core.info(`Deleted previous comment on PR #${pr.number}`) + } catch (e) { + core.warning(`Could not delete previous comment: ${e.message}`) + } + } + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: pr.number, + body, + }) + core.info(`Created new comment on PR #${pr.number}`) diff --git a/.github/workflows/pr-pdf-preview.yml b/.github/workflows/pr-pdf-preview.yml index 2ed712a..bad932e 100644 --- a/.github/workflows/pr-pdf-preview.yml +++ b/.github/workflows/pr-pdf-preview.yml @@ -3,9 +3,9 @@ name: PR PDF Preview on: pull_request: types: [opened, synchronize, reopened, ready_for_review] - branches: [ main ] + branches: [main] paths: - - '**.typ' + - "**.typ" jobs: generate-pdf: @@ -16,21 +16,21 @@ jobs: pull-requests: read steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} - - name: Setup Typst - uses: typst-community/setup-typst@v3 + - name: Setup Typst + uses: typst-community/setup-typst@v3 - - name: Compile Typst document - run: | - typst compile main.typ template-preview.pdf + - name: Compile Typst document + run: | + typst compile main.typ ${{ github.event.repository.name }}-preview.pdf - - name: Upload PDF as artifact - uses: actions/upload-artifact@v4 - with: - name: pr-${{ github.event.pull_request.number }}-preview - path: template-preview.pdf - retention-days: 30 + - name: Upload PDF as artifact + uses: actions/upload-artifact@v4 + with: + name: pr-${{ github.event.pull_request.number }}-preview + path: ${{ github.event.repository.name }}-preview.pdf + retention-days: 30 diff --git a/.github/workflows/release-pdf.yml b/.github/workflows/release-pdf.yml index 8ec00b1..cca2593 100644 --- a/.github/workflows/release-pdf.yml +++ b/.github/workflows/release-pdf.yml @@ -2,9 +2,9 @@ name: Create Release with compiled PDF on: push: - branches: [ main ] + branches: [main] paths: - - '**.typ' + - "**.typ" jobs: create-release: @@ -13,43 +13,45 @@ jobs: contents: write steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Typst - uses: typst-community/setup-typst@v3 - - - name: Compile Typst document - run: | - typst compile main.typ template.pdf - - - name: Upload PDF as artifact - uses: actions/upload-artifact@v4 - with: - name: template-pdf-release - path: template.pdf - retention-days: 90 - - - name: Get current date - id: date - run: echo "date=$(date +'%d/%m/%Y')" >> $GITHUB_OUTPUT - - - name: Create Release with PDF - uses: softprops/action-gh-release@v2 - with: - tag_name: release-${{ github.sha }} - name: Compiled PDF ${{ steps.date.outputs.date }} (${{ github.sha }}) - body: | - 🎓 **Template - PDF ${{ steps.date.outputs.date }} (${{ github.sha }})** - - Automated release created from main branch with the latest version of the course notes. - - **Release Details:** - - Generated from commit: ${{ github.sha }} - - Build date: ${{ steps.date.outputs.date }} - - PDF compiled with latest Typst version - - **Download:** The PDF is attached to this release as `template.pdf` - files: template.pdf - draft: false - prerelease: false + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Typst + uses: typst-community/setup-typst@v3 + + - name: Compile Typst document + run: | + typst compile main.typ ${{ github.event.repository.name }}.pdf + + - name: Upload PDF as artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ github.event.repository.name }}-release + path: ${{ github.event.repository.name }}.pdf + retention-days: 90 + + - name: Get current date and short SHA + id: meta + run: | + echo "date=$(date +'%d/%m/%Y')" >> $GITHUB_OUTPUT + echo "sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT + + - name: Create Release with PDF + uses: softprops/action-gh-release@v2 + with: + tag_name: release-${{ steps.meta.outputs.sha }} + name: Compiled PDF ${{ steps.meta.outputs.date }} (${{ steps.meta.outputs.sha }}) + body: | + 🎓 **${{ github.event.repository.name }} - PDF ${{ steps.meta.outputs.date }} (${{ steps.meta.outputs.sha }})** + + Automated release created from main branch with the latest version of the course notes. + + **Release Details:** + - Generated from commit: ${{ steps.meta.outputs.sha }} + - Build date: ${{ steps.meta.outputs.date }} + - PDF compiled with latest Typst version + + **Download:** The PDF is attached to this release as `${{ github.event.repository.name }}.pdf` + files: ${{ github.event.repository.name }}.pdf + draft: false + prerelease: false diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3a42638 --- /dev/null +++ b/LICENSE @@ -0,0 +1,386 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + +Section 1 -- Definitions. + +a. Adapted Material means material subject to Copyright and Similar +Rights that is derived from or based upon the Licensed Material +and in which the Licensed Material is translated, altered, +arranged, transformed, or otherwise modified in a manner requiring +permission under the Copyright and Similar Rights held by the +Licensor. For purposes of this Public License, where the Licensed +Material is a musical work, performance, or sound recording, +Adapted Material is always produced where the Licensed Material is +synched in timed relation with a moving image. + +b. Adapter's License means the license You apply to Your Copyright +and Similar Rights in Your contributions to Adapted Material in +accordance with the terms and conditions of this Public License. + +c. Copyright and Similar Rights means copyright and/or similar rights +closely related to copyright including, without limitation, +performance, broadcast, sound recording, and Sui Generis Database +Rights, without regard to how the rights are labeled or +categorized. For purposes of this Public License, the rights +specified in Section 2(b)(1)-(2) are not Copyright and Similar +Rights. + +d. Effective Technological Measures means those measures that, in the +absence of proper authority, may not be circumvented under laws +fulfilling obligations under Article 11 of the WIPO Copyright +Treaty adopted on December 20, 1996, and/or similar international +agreements. + +e. Exceptions and Limitations means fair use, fair dealing, and/or +any other exception or limitation to Copyright and Similar Rights +that applies to Your use of the Licensed Material. + +f. Licensed Material means the artistic or literary work, database, +or other material to which the Licensor applied this Public +License. + +g. Licensed Rights means the rights granted to You subject to the +terms and conditions of this Public License, which are limited to +all Copyright and Similar Rights that apply to Your use of the +Licensed Material and that the Licensor has authority to license. + +h. Licensor means the individual(s) or entity(ies) granting rights +under this Public License. + +i. Share means to provide material to the public by any means or +process that requires permission under the Licensed Rights, such +as reproduction, public display, public performance, distribution, +dissemination, communication, or importation, and to make material +available to the public including in ways that members of the +public may access the material from a place and at a time +individually chosen by them. + +j. Sui Generis Database Rights means rights other than copyright +resulting from Directive 96/9/EC of the European Parliament and of +the Council of 11 March 1996 on the legal protection of databases, +as amended and/or succeeded, as well as other essentially +equivalent rights anywhere in the world. + +k. You means the individual or entity exercising the Licensed Rights +under this Public License. Your has a corresponding meaning. + +Section 2 -- Scope. + +a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + +b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + +a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + +a. for the avoidance of doubt, Section 2(a)(1) grants You the right +to extract, reuse, reproduce, and Share all or a substantial +portion of the contents of the database; + +b. if You include all or a substantial portion of the database +contents in a database in which You have Sui Generis Database +Rights, then the database in which You have Sui Generis Database +Rights (but not its individual contents) is Adapted Material; and + +c. You must comply with the conditions in Section 3(a) if You Share +all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + +a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE +EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS +AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF +ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, +IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, +WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, +ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT +KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT +ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + +b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE +TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, +NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, +INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, +COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR +USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN +ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR +DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR +IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + +c. The disclaimer of warranties and limitation of liability provided +above shall be interpreted in a manner that, to the extent +possible, most closely approximates an absolute disclaimer and +waiver of all liability. + +Section 6 -- Term and Termination. + +a. This Public License applies for the term of the Copyright and +Similar Rights licensed here. However, if You fail to comply with +this Public License, then Your rights under this Public License +terminate automatically. + +b. Where Your right to use the Licensed Material has terminated under +Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + +c. For the avoidance of doubt, the Licensor may also offer the +Licensed Material under separate terms or conditions or stop +distributing the Licensed Material at any time; however, doing so +will not terminate this Public License. + +d. Sections 1, 5, 6, 7, and 8 survive termination of this Public +License. + +Section 7 -- Other Terms and Conditions. + +a. The Licensor shall not be bound by any additional or different +terms or conditions communicated by You unless expressly agreed. + +b. Any arrangements, understandings, or agreements regarding the +Licensed Material not stated herein are separate from and +independent of the terms and conditions of this Public License. + +Section 8 -- Interpretation. + +a. For the avoidance of doubt, this Public License does not, and +shall not be interpreted to, reduce, limit, restrict, or impose +conditions on any use of the Licensed Material that could lawfully +be made without permission under this Public License. + +b. To the extent possible, if any provision of this Public License is +deemed unenforceable, it shall be automatically reformed to the +minimum extent necessary to make it enforceable. If the provision +cannot be reformed, it shall be severed from this Public License +without affecting the enforceability of the remaining terms and +conditions. + +c. No term or condition of this Public License will be waived and no +failure to comply consented to unless expressly agreed to by the +Licensor. + +d. Nothing in this Public License constitutes or may be interpreted +as a limitation upon, or waiver of, any privileges and immunities +that apply to the Licensor or You, including from the legal +processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/README.md b/README.md new file mode 100644 index 0000000..03c954b --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Dispensa di GPU Computing + +Appunti del corso di [_GPU Computing_](https://myariel.unimi.it/course/view.php?id=2942) (a.a. 2025/26), tenuto dal Prof. _Giuliano Grossi_, Corso di Laurea in Informatica Magistrale, Università degli Studi di Milano. + +Realizzati da [Luca Corradini](https://github.com/LucaCorra02), [Matteo Zagheno](https://github.com/tsagae), con il contributo di [altri collaboratori](https://github.com/Favo02/algoritmi-e-complessita/graphs/contributors). + +Questi appunti sono open source: [github.com/Favo02/algoritmi-e-complessita](https://github.com/Favo02/algoritmi-e-complessita) con licenza [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/). + +Le contribuzioni e correzioni sono ben accette attraverso Issues o Pull Requests. + +> [!CAUTION] +> Controllare soprattutto Issues e commenti `TODO` + +## PDF Compilato + +Il documento è scritto in [Typst](https://typst.app/) _(che è meglio di LaTeX)_. + +- Un PDF compilato è generato automaticamente ad ogni push su `main` e disponibile nelle [release](https://github.com/Favo02/algoritmi-e-complessita/releases). +- Oppure è possibile compilare localmente con il comando: `typst compile main.typ` + +## Workflow + +Il push su `main` (e quindi la generazione del PDF) è protetto: + +- è necessario creare una pull request per proporre modifiche +- è richiesta una review da parte di un collaboratore per il merge +- viene generato automaticamente un PDF temporaneo per ogni pull request diff --git a/chapters/Lezione-7.typ b/chapters/Lezione-7.typ index 1229c14..08bc5da 100644 --- a/chapters/Lezione-7.typ +++ b/chapters/Lezione-7.typ @@ -28,7 +28,7 @@ Per inizializzare un tensore da dei dati, possiamo: print(x_data) ``` -#nota()[ +#note()[ Il tipo viene dedotto automaticamente dall'interprete. ] @@ -46,7 +46,7 @@ x_ones = torch.ones_like(x_data) # [[1,1],[1,1]] x_rand = torch.rand_like(x_data, dtype=torch.float) #[[1.2,0.3],[0.23,2.3]] ``` -#attenzione()[ +#warning()[ Quando si converte un vettore `numpy` ad un `tensor` ci sono due aspetti da considerare: - `PyTorch` di default usa i `float32`, mentre `numpy` usa i `float64`. Se ereditassimo direttamente da `numpy` ci potrebbe essere un errore di conversione, bisgona *riconvertire* corretamente i dati. @@ -76,7 +76,7 @@ Esistono inoltre degli operatori caricati, ad esempio: - `@`: utilizzato per *operazioni di algebra lineare*, come il prodotto matriciale - `*`: utilizzato per *element-wise product*, ad esempio prodotto degli elementi cella per cella di un vettore -#attenzione()[ +#warning()[ La scrittura `tensor.add_(5)` serve per le operazioni *in place*. Esse modificano il tensore in maniera diretta. ] @@ -113,7 +113,7 @@ Nell'esempio sorpa il la seconda dimensione del batch del tenosore $b$ viene pos ], ) -#attenzione()[ +#warning()[ Non tutte le forme di tensori sono compatibili. Affinché il broadcasting funzioni, `PyTorch` confronta le dimensioni dei due tensori partendo da *destra verso sinistra* (dall'ultima dimensione alla prima). Due dimensioni sono *compatibili* se: - Sono uguali - Una delle due è $1$. @@ -121,7 +121,7 @@ Nell'esempio sorpa il la seconda dimensione del batch del tenosore $b$ viene pos Se un tensore ha meno dimensioni dell'altro, `PyTorch` aggiunge virtualmente delle dimensioni $1$ a sinistra. ] -#esempio()[ +#example()[ Supponiamo di avere il seguente codice: ```py A = torch.tensor([[1.], [2.], [3.], [4.]]) @@ -250,7 +250,7 @@ Si tratta di una notazione per effetuare in modo coinciso operazioni compoment-w - i tensori su cui si deve operare - il tensore risultato -#nota()[ +#note()[ L'operazione viene eseguita su tutti gli indici che non appaiono tra gli indici del risultato. ] @@ -313,11 +313,11 @@ La soluzione prende il nome di $mg("batch norm")$. Essa va a normalizzare l'inp $ y = gamma hat(x)+ beta $ - #nota()[ + #note()[ Se venisse forzata media $0$ e varianza $1$, potremmo limitare la capacità della rete. $gamma$ e $beta$ permettono alla rete di imparare se le serve una distribuzione diversa (o addirittura di annullare la normalizzazione se necessario). ] -#informalmente()[ +#informally()[ I vantaggi introdotti sono due: - *Velocità*: Permette di usare Learning Rate molto più alti (convergenza rapida). @@ -341,7 +341,7 @@ $ sigma^2_c = 1 / ("BHW") sum_(b,h,w) (x_(b,c,h,w) - mu_c)^2 $ -#nota()[ +#note()[ I canali $C$ rappresentano le features estratte (embedding). La BatchNorm normalizza ogni feature indipendentemente dalle altre, ma coerentemente su tutto il batch $B$ e su tutta l'immagine spaziale. ] @@ -499,14 +499,14 @@ Per tensori con più di 2 dimensioni, `transpose(dim0, dim1)` scambia le dimensi caption: [Transpose su tensori 4D: scambio di dimensioni specifiche], ) -#informalmente()[ +#informally()[ - `transpose(0, 1)`: scambia le *prime due dimensioni* (es. batch e canali). Utile quando si vuole riorganizzare l'ordine dei batch rispetto ai canali. - `transpose(0, 2)`: scambia la *prima e terza dimensione* (es. batch e altezza). Permette di riordinare i dati spaziali rispetto al batch. La trasposizione è fondamentale in operazioni come la convoluzione trasposta o quando si passano dati tra layer con formati diversi (es. NCHW ↔ NHWC). ] -#nota()[ +#note()[ La trasposizione *non copia i dati* in memoria, ma cambia solo l'interpretazione degli indici. Questo può rendere il tensore *non contiguo* in memoria. ] @@ -580,7 +580,7 @@ Esempio di utilizzo di `view`: caption: [View: riorganizzazione dei dati senza copia], ) -#attenzione()[ +#warning()[ `View` effettua il reshape solamente di tensori contigui in memoria. ```py x = torch.arange(12).view(3,4) @@ -589,7 +589,7 @@ Esempio di utilizzo di `view`: ``` La matrice viene trasposta (scambio righe colonne) ed è per quello che non trova più la contiguità. - #informalmente()[ + #informally()[ Il concetto di contiguità significa: _I numeri che sono vicini nella matrice (logica) sono vicini anche nella striscia di memoria RAM (fisica)_. ] @@ -659,7 +659,7 @@ L'operazione `expand()` *replica i dati* lungo le dimensioni specificate *senza caption: [Expand: replicazione dei dati mediante broadcasting], ) -#nota()[ +#note()[ `expand()` *non alloca nuova memoria* ma semplicemente modifica gli stride del tensore per far sì che gli stessi dati vengano "visti" più volte. Questa operazione è molto efficiente rispetto a una vera copia dei dati. *Differenze chiave*: @@ -834,7 +834,7 @@ result = torch.stack([x1, x2], dim=1) caption: [torch.stack: crea una nuova dimensione impilando i tensori], ) -#nota()[ +#note()[ *Differenza tra `cat` e `stack`*: - `torch.cat`: unisce tensori lungo una dimensione *esistente*, aumentando la size di quella dimensione - `torch.stack`: crea una *nuova dimensione* e posiziona i tensori lungo quella dimensione @@ -928,7 +928,7 @@ splits = torch.split(x, split_size_or_sections=[1, 2, 1], dim=0) caption: [torch.split: divide il tensore in parti di dimensioni specificate], ) -#informalmente()[ +#informally()[ *Quando usare chunk vs split*: - `chunk`: quando vuoi dividere in un *numero fisso di parti* (es. dividere un batch in 4 parti uguali) - `split`: quando vuoi *controllare la dimensione* di ogni parte (es. train/validation split con proporzioni specifiche) diff --git a/chapters/Lezione10.typ b/chapters/Lezione10.typ index 8f7abb3..c731940 100644 --- a/chapters/Lezione10.typ +++ b/chapters/Lezione10.typ @@ -17,7 +17,7 @@ $ nabla f(x) = vec((partial f)/(partial x_1), (partial f)/(partial x_2), dots.v, (partial f)/(partial x_D)) $ -#nota()[ +#note()[ Il gradiente trasforma una funzione scalare in un *campo vettoriale*: *ad ogni punto $x$* dello spazio viene associato un vettore *$nabla f(x)$*. Nel contesto del deep learning: @@ -121,7 +121,7 @@ Nel contesto della visualizzazione 3D: caption: [Il gradiente $nabla f$ ($mr("rosso")$) è il vettore che si ottiene combinando le due derivate parziali: la componente $mb("blu")$ $(partial f)/(partial x)$ (orizzontale) e la componente $mg("verde")$ $(partial f)/(partial y)$ (verticale). Nel piano 2D $(x,y)$, il gradiente indica la direzione di massima crescita di $f$.], ) -#nota()[ +#note()[ *Interpretazione geometrica del gradiente*: - *Direzione*: Il gradiente punta nella direzione in cui la funzione cresce più rapidamente @@ -139,7 +139,7 @@ Un concetto fondamentale è che il gradiente è *perpendicolare* alle curve di l *Perpendicolarità*: In ogni punto, il vettore gradiente $nabla f$ è *perpendicolare* (ortogonale, a 90°) alla curva di livello che passa per quel punto. -#attenzione()[ +#warning()[ *Importante*: La perpendicolarità è nel *piano $(x, y)$*, non nello spazio 3D! - Il gradiente è un vettore 2D: $nabla f(x, y) = vec((partial f)/(partial x), (partial f)/(partial y))$ @@ -149,7 +149,7 @@ Un concetto fondamentale è che il gradiente è *perpendicolare* alle curve di l Quando visualizziamo la superficie 3D, "proiettiamo" mentalmente il gradiente sul piano di base per vedere la sua perpendicolarità alle curve di livello. ] -#esempio()[ +#example()[ Consideriamo la funzione quadratica in due variabili: $ f(x_1, x_2) = x_1^2 + 3x_1 x_2 + 2x_2^2 @@ -264,7 +264,7 @@ Il gradiente ha diverse interpretazioni geometriche fondamentali: 3. * Magnitudine come tasso di crescita*: Il modulo $||nabla f(x_0)||$ rappresenta il *tasso di crescita massimo* di $f$ in $x_0$. -#esempio()[ +#example()[ Consideriamo il paraboloide: $ f(x, y) = x^2 + y^2 @@ -342,13 +342,13 @@ $ cal(L)_c = {x in R^D : f(x) = c} $ -#nota()[ +#note()[ *Proprietà fondamentale*: Il gradiente $nabla f(x_0)$ in un punto $x_0$ è *ortogonale* alla curva di livello passante per $x_0$. Questo significa che il gradiente punta nella direzione che *esce* dalla curva di livello, verso valori crescenti di $f$. ] -#attenzione()[ +#warning()[ Le curve di livello sono fondamentali nell'ottimizzazione: - Un *passo di gradient descent* ci sposta da una curva di livello a un'altra con valore inferiore @@ -360,7 +360,7 @@ $ Si tratta del teorema fondamentale che giustifica l'uso del gradiente nell'ottimizzazione. -#teorema("Massima Discesa")[ +#theorem(title: "Massima Discesa")[ Dato un punto $x_0 in R^D$ e una funzione differenziabile $f: R^D -> R$, tra tutte le direzioni unitarie $d$ (con $||d|| = 1$), la direzione di *massima discesa* (che minimizza $f$ localmente) è data da: $ d^* = -(nabla f(x_0)) / (||nabla f(x_0)||) @@ -368,7 +368,7 @@ Si tratta del teorema fondamentale che giustifica l'uso del gradiente nell'ottim Ovvero, il *negativo del gradiente* indica la direzione in cui la funzione *decresce più rapidamente*. ] -#dimostrazione()[ +#proof()[ L'obbiettivo della dimostrazione è trovare una direzione unitaria $d$ che minimizza la funzione $f$ localmente intorno a $x_0$. Per la dimostrazione è necessario considerare lo *sviluppo di Taylor* al primo ordine di $f$ intorno a $x_0$: @@ -382,7 +382,7 @@ Si tratta del teorema fondamentale che giustifica l'uso del gradiente nell'ottim La nuova _altezza_ della funzione dopo il passo ($f(x_0 + alpha d)$) è data dalla vecchia altezza ($f(x_0)$) più un termine di variazione ($alpha nabla f(x_0)^T d$). - #nota()[ + #note()[ Lo sviluppo di Taylor ci dice che per piccoli spostamenti $alpha d$ da $x_0$, la funzione cambia approssimativamente di: $ Delta f approx alpha nabla f(x_0)^T d @@ -412,7 +412,7 @@ Si tratta del teorema fondamentale che giustifica l'uso del gradiente nell'ottim ] -#nota()[ +#note()[ *Conclusione*: Muoversi nella direzione $-nabla f(x_0)$ garantisce la *massima riduzione* della funzione $f$ in un intorno di $x_0$. Questo è il principio alla base del *Gradient Descent*! @@ -436,12 +436,12 @@ dove: - Aggiorna i parametri: $x_(k+1) = x_k - alpha_k g_k$ - Controlla convergenza: se $||g_k|| < epsilon$, termina -#nota()[ +#note()[ *Criteri di convergenza*: - *Condizione ideale*: $nabla f(x_k) = bold(0)$ (punto stazionario) - *Condizione pratica*: $||nabla f(x_k)|| < epsilon$ per una soglia piccola $epsilon > 0$ - #attenzione()[ + #warning()[ Un gradiente nullo può indicare: - Un *minimo locale* (desiderato) - Un *massimo locale* (indesiderato) @@ -451,7 +451,7 @@ dove: ] ] -#esempio()[ +#example()[ Minimizziamo $f(x, y) = x^2 + 4y^2$ partendo da $(x_0, y_0) = (3, 2)$. *Gradiente*: @@ -477,7 +477,7 @@ dove: Convergenza verso il minimo $(0, 0)$ dove $f(0,0) = 0$. ] -#attenzione()[ +#warning()[ *Scelta del Learning Rate $alpha$*: - $alpha$ troppo *piccolo*: convergenza molto lenta, molte iterazioni @@ -565,7 +565,7 @@ $ h'(x) = f'(g(x)) dot g'(x) $ -#esempio()[ +#example()[ Consideriamo $h(x) = (x^2 + 1)^3$. Possiamo vedere questa funzione come composizione: @@ -594,7 +594,7 @@ dove: - $J_g (f(x)) in R^(k times m)$: Jacobiano di $f$ valutato in $g(x)$ - $J_f (x) in R^(m times n)$: Jacobiano di $g$ valutato in $x$ -#nota()[ +#note()[ *Interpretazione per il Deep Learning*: In una rete neurale con $L$ layer, l'output finale è una composizione di funzioni: @@ -607,7 +607,7 @@ dove: Questo è il principio alla base della *backpropagation* ] -#esempio()[ +#example()[ Consideriamo una rete neurale minima con due layer (input e output scalari, un solo neurone nascosto, dimensone $1$): - Layer 1: $z = w_1 x + b_1$ (trasformazione lineare) @@ -646,7 +646,7 @@ dove: ] -#attenzione()[ +#warning()[ La chain rule è il meccanismo fondamentale che permette al *backpropagation* di propagare i gradienti all'indietro attraverso tutti i layer della rete, partendo dalla loss fino ai primi parametri. ] @@ -674,7 +674,7 @@ $ "Riga" i: nabla f_i (x) = vec((partial f_i)/(partial x_1), (partial f_i)/(partial x_2), dots, (partial f_i)/(partial x_n)) $ -#nota()[ +#note()[ *Confronto con il Gradiente*: - Il *gradiente* $nabla f$ si applica a funzioni scalari: $f: R^n -> R$ (produce un vettore $in R^n$) @@ -686,7 +686,7 @@ $ $ ] -#esempio()[ +#example()[ Consideriamo $F: R^2 -> R^2$ definita da: $ @@ -723,7 +723,7 @@ $ -#esempio()[ +#example()[ *Jacobiano di un layer fully-connected*: Un layer lineare in una rete neurale è definito da: @@ -770,7 +770,7 @@ $ ) $ -#informalmente()[ +#informally()[ - $f$ è l'output di un layer con $m$ neuroni (un vettore). - $g$ è la funzione di Loss finale che prende tutti questi output e calcola un numero unico (l'errore scalare $h$). @@ -802,7 +802,7 @@ Ogni matrice fa fare all'errore un _salto_ all'indietro di un layer. -#nota()[ +#note()[ *Dimensioni nel prodotto*: $ underbrace(R^(k times m), J_g) dot underbrace(R^(m times n), J_f) = underbrace(R^(k times n), J_h) @@ -811,7 +811,7 @@ Ogni matrice fa fare all'errore un _salto_ all'indietro di un layer. Le dimensioni intermedie $m$ si cancellano nel prodotto matriciale, come richiesto dalla composizione di funzioni. ] -#esempio()[ +#example()[ Dato un vettore di input $overline(x) = (x_1, x_2) in R^2$ e due funzioni $f$ e $g$, dove: $ f: R^2 -> R^2 \ @@ -839,20 +839,20 @@ Ogni matrice fa fare all'errore un _salto_ all'indietro di un layer. $ ] -#attenzione()[ +#warning()[ Durante la backpropagation, il gradiente della loss viene propagato all'indietro attraverso la rete moltiplicando ripetutamente gli Jacobiani dei vari layer. Questo è computazionalmente efficiente grazie alla *struttura matriciale degli Jacobiani*. ] == Esempio completo -#esempio()[ +#example()[ Supponiamo di avere un modello di *classificazione binaria* (comunemente chiamato *regressione logistica*) dove il dataset è dato da: $ D = {(x_i, y_i)}_(i=1)^N\ "dove" x_i in R^D, y_i in {0,1} $ - #nota()[ + #note()[ *Terminologia*: Nonostante il nome "regressione logistica", si tratta di un algoritmo di *classificazione*, non di regressione! - *Input*: features $x_i in R^D$ (vettori continui) @@ -988,7 +988,7 @@ Ogni matrice fa fare all'errore un _salto_ all'indietro di un layer. - Se $w^T z + b > 0$: classe 1 (giallo) - Se $w^T z + b < 0$: classe 0 (rosso) - #attenzione()[ + #warning()[ Un modello *puramente lineare* può commettere errori quando i dati non sono linearmente separabili, come mostrato dai punti cerchiati nel grafico. La funzione *sigmoide* $sigma(z)$ trasforma l'output lineare in una probabilità, permettendo al modello di gestire meglio l'incertezza nelle zone di confine. ] @@ -1169,7 +1169,7 @@ Ogni matrice fa fare all'errore un _salto_ all'indietro di un layer. caption: [Grafo computazionale dettagliato mostrando le singole operazioni elementari e le rispettive derivate parziali. La chain rule permette di calcolare i gradienti rispetto ai parametri $w$ e $b$ moltiplicando le derivate parziali lungo il percorso.], ) - #nota()[ + #note()[ *Backpropagation tramite Chain Rule*: Per calcolare il gradiente della loss rispetto ai parametri, moltiplichiamo le derivate parziali lungo il percorso nel grafo (dalla loss verso i parametri): @@ -1211,7 +1211,7 @@ $ w_(t+1) = w_t - eta 1/(|Beta_t|) sum_(n in B_t) gradient_w mr(l)(f(x_n;w_t),y_n) $ -#nota()[ +#note()[ L'algoritmo è stocastic in quanto ogni aggiornamento dei pesi usa un sample random, viene introdotto del *rumore* nel calcolo del gradiente ] @@ -1381,7 +1381,7 @@ Un *grafo computazionale* è un grafo orientato aciclico (DAG - Directed Acyclic I gradienti vengono *accumulati* nell'attributo `.grad` dei tensori. La funzione di `backward()` permette di attraversare il grafo per calcolare i gradienti. -#nota()[ +#note()[ Il grafo cattura la *struttura della funzione* e le *dipendenze* tra le variabili. Questo permette di calcolare automaticamente i gradienti usando la chain rule. ] @@ -1396,7 +1396,7 @@ Il grafo viene costruito *dinamicamente* durante l'esecuzione del codice (*dynam 3. *Registrazione delle dipendenze*: PyTorch registra quale operazione ha generato quale tensore 4. *Tracciamento automatico*: Il grafo viene costruito man mano che il codice viene eseguito -#esempio()[ +#example()[ Consideriamo il seguente codice PyTorch: ```python @@ -1506,7 +1506,7 @@ Il grafo computazionale supporta due modalità di attraversamento: - Si applicano le regole di derivazione usando la chain rule - *Risultato*: calcolo dei gradienti rispetto a tutti i parametri -#esempio()[ +#example()[ Continuando l'esempio precedente, calcoliamo i gradienti con `y.backward()`: ```python @@ -1540,7 +1540,7 @@ Il grafo computazionale supporta due modalità di attraversamento: Un aspetto importante della differenziazione automatica in PyTorch è l'*accumulazione dei gradienti*. -#attenzione()[ +#warning()[ Per default, PyTorch *accumula* i gradienti nel campo `.grad` di ogni tensore. Questo significa che chiamare `.backward()` multiple volte *somma* i nuovi gradienti a quelli esistenti. *Conseguenza pratica*: Nel training loop è necessario chiamare `optimizer.zero_grad()` o `tensor.grad.zero_()` prima di ogni backward pass per pulire i gradienti dell'iterazione precedente. @@ -1558,7 +1558,7 @@ La differenziazione automatica attraverso grafi computazionali offre numerosi va *4. Automaticità*: Il programmatore non deve derivare manualmente le formule dei gradienti -#nota()[ +#note()[ *Complessità computazionale*: - *Forward pass*: $O(n)$ dove $n$ è il numero di operazioni @@ -1592,7 +1592,7 @@ for epoch in range(n_epochs): optimizer.step() # Aggiorna i pesi usando i gradienti ``` -#nota()[ +#note()[ *Perché `optimizer.zero_grad()`?* PyTorch *accumula* i gradienti per default. Senza azzerarli ad ogni iterazione, i gradienti si sommerebbero a quelli delle iterazioni precedenti, portando a risultati errati. @@ -1646,7 +1646,7 @@ std = X.std(dim=0) # Deviazione standard per feature X_normalized = (X - mean) / std ``` -#nota()[ +#note()[ In fase di *inferenza*, si usano media e varianza calcolate sul *training set*, non sui nuovi dati. ] @@ -1684,7 +1684,7 @@ x = bn(x) # Normalizzazione x = activation(x) # Funzione di attivazione ``` -#attenzione()[ +#warning()[ *Comportamento Train vs Eval*: - *Training*: statistiche ($mu, sigma^2$) calcolate sul batch corrente @@ -1726,7 +1726,7 @@ x = ln(x) # Normalizzazione per sample x = activation(x) ``` -#nota()[ +#note()[ *Quando usare cosa?* - *BatchNorm*: ideale per CNN e reti fully-connected con batch grandi @@ -1749,7 +1749,7 @@ x = activation(x) caption: [Confronto tra le principali tecniche di normalizzazione. BatchNorm normalizza lungo il batch, LayerNorm lungo le feature di ogni sample.], ) -#informalmente()[ +#informally()[ *Regola pratica*: 1. *Sempre* normalizzare gli input (standardizzazione) @@ -1757,4 +1757,3 @@ x = activation(x) 3. Usare *LayerNorm* per Transformer e quando i batch sono piccoli 4. Posizionare la normalizzazione *prima* o *dopo* l'attivazione (dipende dall'architettura) ] - diff --git a/chapters/Lezione2-Gerarchia-Thread.typ b/chapters/Lezione2-Gerarchia-Thread.typ index 3259d18..749f51c 100644 --- a/chapters/Lezione2-Gerarchia-Thread.typ +++ b/chapters/Lezione2-Gerarchia-Thread.typ @@ -17,7 +17,7 @@ Pensare in parallelo, significa avere chiaro quali *feature* la *GPU* espone al - gestire le *sincronizzazioni*. I thread a volte potrebbero dover cooperare nella GPU. Bisogna effettuare una sincronizzazione all'interno dei blocchi logici di thread. CUDA permette al programmatore di gestire i thread e la memoria dati. -#attenzione[ +#warning[ Le operazioni di lancio del kernel sono sempre asincrone. Mentre le operazioni in memoria sono solitamente sincrone. Questo per garantire l'integrità dei dati. ] @@ -38,13 +38,13 @@ La struttura a _blocchi_ permette alla GPU di distribuire il lavoro. I blocchi v Sia le griglie che i blocchi possono avere una *dimensione* ($1D$, $2D$ o $3D$). -#nota()[ +#note()[ Sebbene la memoria fisica della GPU sia sempre lineare (una lunga sequenza monodimensionale di byte), per i programmatori è difficile ragionare solo in termini lineari se il problema da risolvere è di carattere geometrico. Per questo motivo si usa una organizzazione logica astratta su più dimensioni. ] La scelta del numero di dimensioni avviene in base ai dati che si vuole elaborare. -#nota[ +#note[ In generale si usa la stessa dimensione sia per le griglie che i blocchi ] @@ -61,7 +61,7 @@ Le dimensioni vengono gestite nel seguente modo: Per ottenere il numero totale di thread basta moltiplicare tutte le dimensioni di grid e block tra di loro. -#attenzione[ +#warning[ Il numero di thread *massimo* in un blocco è *$1024$* ] @@ -71,7 +71,7 @@ Un *blocco* è quindi un gruppo di thread che possono cooperare tra loro (anche - *Block-local synchronization* - *Block-local shared memory* -#nota()[ +#note()[ Tutti i thread in una griglia condividono lo stesso spazio di memoria ] @@ -155,7 +155,7 @@ $ ], ) -#esempio()[ +#example()[ Indicizzazione: $ "ID"_("th") = underbrace({0,1,2}, "indice blocco") * 4 + underbrace({0,1,2,3}, "thread id locale") @@ -180,7 +180,7 @@ Spesso è necessario un controllo quando la dim di griglia non eccede con quella if (ix < "blockDim".x AND ix < "blockDim".y) // va bene ``` -#attenzione()[ +#warning()[ Il controllo è *obbligatorio*. Siccome il kernel accetta due dimensioni (numero di blocchi per griglia, numero di thread per blocco) può essere che la divisione logica non sia intera (approssimazione per eccesso con `ceil()`). Il controllo evita accessi _out of bound_ sulla struttura originale. ] @@ -272,8 +272,8 @@ Spesso è necessario un controllo quando la dim di griglia non eccede con quella ], ) -#esempio( - figure( +#example[ + #figure( { import cetz.draw: * @@ -305,16 +305,8 @@ Spesso è necessario un controllo quando la dim di griglia non eccede con quella for tx in range(3) { let cx = x + tx * cell_size let cy = y - ty * cell_size - rect( - (cx, cy - cell_size), - (cx + cell_size, cy), - fill: color, - stroke: black, - ) - content( - (cx + cell_size / 2, cy - cell_size / 2), - text(size: 8pt, weight: "bold")[#tx,#ty], - ) + rect((cx, cy - cell_size), (cx + cell_size, cy), fill: color, stroke: black) + content((cx + cell_size / 2, cy - cell_size / 2), text(size: 8pt, weight: "bold")[#tx,#ty]) } } @@ -348,9 +340,7 @@ Spesso è necessario un controllo quando la dim di griglia non eccede con quella let color = if i < 3 { color_b00 } else { color_b10 } // Adjust color based on Y coordinate - if start_idx >= 18 { - color = if i < 3 { color_b01 } else { color_b11 } - } + if start_idx >= 18 { color = if i < 3 { color_b01 } else { color_b11 } } rect((cx, y), (cx + cell_w, y + 0.5), fill: color, stroke: black) content((cx + cell_w / 2, y + 0.25), text(size: 6pt, weight: "bold")[#(start_idx + i)]) @@ -389,7 +379,7 @@ Spesso è necessario un controllo quando la dim di griglia non eccede con quella $"Grid" 2 * 2$, $"Block" 3 * 3$ ], ), -) +] /* #figure( @@ -476,7 +466,7 @@ Prorietà di un kernel. Sono dei qualificatori: - Può essere chiamata solo dal device. Impostando la modalità *`inline=True`*, il compilatore inserisce il codice della funzione device all'interno della funzione kernel, risparmiando così i costi relativi alla chiamata. - Può avere un valore di ritorno - #attenzione()[ + #warning()[ Le funzioni ``` device``` *non* sono un kernel ] @@ -509,11 +499,11 @@ Prorietà di un kernel. Sono dei qualificatori: "blockspergrid" * "threadsperblock" $ -#nota()[ +#note()[ In generale, una volta fissato il numero di thread per blocco, non è corretto calcolare il numero di blocchi necessari con la divisione intera è meglio utilizzare una divisone e approsimare con ``` ceil```. ] -#attenzione()[ +#warning()[ L'esecuzione del kernel avviene in modo asincrono. Lato host bisogna usare ``` cuda.synchronize()``` per aspettare i risultati dai thread. ] @@ -531,7 +521,7 @@ Numba fornisce anche due funzioni per calcolare la *posizione assoluta* di un *t La GPU e la CPU hanno due memorie separate. -#nota()[ +#note()[ Il trasferimento di dati può essere in molti casi il bottleneck ] @@ -560,7 +550,7 @@ numba.cuda.device_array( il vantaggio è che la memoria viene allocata solo lato device, inoltre non vengono ricopiati automaticamente sull'host. -#nota()[ +#note()[ Stesso comportamento di ``` numpy.empty()``` lato host ] @@ -602,7 +592,7 @@ $ v[i][j] = 1 "se" "is_Fib"(i + j) = "True" $ -#nota()[ +#note()[ - Il bound degli indici nel kernel è *obbligatorio*. A casua della funzione `ceil` potremmo eseguire più blocchi del necessario. - Per quanto riguarda la divisione in blocchi, le GPU moderne eseguono i thread in gruppi indivisibili chiamati *Warp*. Le GPU moderne preferiscono blocchi con più warp per blocco. Solitamente si opera per blocchi da $16 dot 16 = 256$ thread, oppure $32 dot 8 = 256$ thread (sfrutta la coalescenza della memoria). ] diff --git a/chapters/Lezione3-Warp.typ b/chapters/Lezione3-Warp.typ index c419476..6d60b3e 100644 --- a/chapters/Lezione3-Warp.typ +++ b/chapters/Lezione3-Warp.typ @@ -16,7 +16,7 @@ Vale la seguente mappatura: - un blocco viene assegnato ad un singolo *SM* (non può essere spalmato su più SM) -#nota()[ +#note()[ Un singolo SM può eseguire più blocchi contemporaneamente (se possiede abbastanza risorse) ] @@ -177,13 +177,13 @@ Un *warp* è un gruppo di $32$ thread consecutivi (ID sequenziali) che vengono e Se i blocchi sono una suddivisione solamente a livello logico, un *warp* è un concetto fisico, ovvero come l'hardware organizza ed esegue fisicamente i thread. Ogni blocco viene linearizzato in più warp seguendo un approccio *row-major*, prima sulla dimensione $x$, poi $y$ e infine $z$. -#informalmente()[ +#informally()[ L'hardware ignora l'organizzazione a blocchi dei thread. Esso vede la griglia come sequenze di warp. L'ordine 2D "logico" viene linearizzato in $32$ thread sequenziali. ] L'ideale è che i blocchi siano multipli di $32$ in modo da permettere una buona mappatura sull'hardware. -#esempio()[ +#example()[ Supponendo un blocco di $128$ thread, verranno creati $4$ warp da $32$ thread. ] @@ -261,7 +261,7 @@ L'ideale è che i blocchi siano multipli di $32$ in modo da permettere una buona ], ) -#nota()[ +#note()[ La dimensione di un warp è una costante su varie architetture, in modo tale da garantire l'interoperabilità. ] @@ -285,7 +285,7 @@ Entra in funzione quando l'Host (CPU) lancia un Kernel: - se la GPU è occupata o piccola, assegna pochi blocchi e mette gli altri in coda. -#nota()[ +#note()[ Una volta che un blocco è assegnato a un SM, ci rimane fino alla fine della sua esecuzione. *Non* può "migrare" su un altro SM. ] @@ -424,13 +424,13 @@ I blocchi vengono assegnati in maniera *dinamica* agli SM (che contengono molte L'SM ha il compito di gestire uno o più blocchi. Un SM *accetta un nuovo blocco solo se ha abbastanza risorse* hardware (registri e Shared Memory) per ospitare tutti i thread di quel blocco. -#nota()[ +#note()[ I blocchi in una griglia sono tra di loro *indipendenti*, non si hanno garanzie sull'ordine di esecuzione. ] Appena un blocco viene caricato sull'SM, viene logicamente suddiviso in Warps. -#attenzione()[ +#warning()[ È importante che un certo blocco *non* dipenda dal risultato di altri blocchi. La cooperazione inoltre avviene solamente all'interno del blocco (tranne casi particolari). ] @@ -459,7 +459,7 @@ $ "Active warp" / "max possible warp per SM" $ -#nota()[ +#note()[ Se saturo il numero di risorse per un thread singolo (tante varibili che saturano i registri), lo sheduling generale soffre. Un'alta occupazione non garantisce per forza delle buone performance. @@ -474,7 +474,7 @@ Le operazioni di *branch* rendono inefficiente il sistema. Quando una branch vie I gruppi di thread così creati, non sono più eseguiti in modo parallelo ma in modo sequenziale. -#esempio()[ +#example()[ #figure( grid( columns: (0.5fr, 1.0fr), @@ -596,7 +596,7 @@ I gruppi di thread così creati, non sono più eseguiti in modo parallelo ma in La soluzione é *riorganizzare i thread a livello di warp*, in modo che non si verifichino attese. -#attenzione()[ +#warning()[ *Non* è obbligatorio che ci sia un assocazione 1:1 thread e dati, ovvero il primo dato corrisponde al thread con $"ID" 1$. L'idea è lavorare modulo la dimensione di warp. ] @@ -617,11 +617,11 @@ In CUDA la sincronizzazione può essere fatta su più livelli: - livello di *warp* ```c __syncwarp()```. Sincronizza i thread all'interno di warp. -#nota()[ +#note()[ Utilizzeremo principalmente la sincronizzazione a livello di blocco. ] -#attenzione()[ +#warning()[ Inserire una direttiva di sincronizzazione ``` __synchtreads()``` all'interno di un branch ``` if-else``` può causare *deadlock* se i thread del blocco vengono divisi in due gruppi. ```py if threadIdx.x < 512: @@ -635,7 +635,7 @@ In CUDA la sincronizzazione può essere fatta su più livelli: Siccome nelle recenti architetture ogni thread possiede un proprio PC (program counter), la *GPU* è in grado di *saltare tra i rami ``` if e else``` dello stesso warp* in modo flessibile. La direttiva ```c __syncwarp()``` è lo strumento per gestire i punti di ritorno. -#esempio()[ +#example()[ Sulle vecchie GPU, senza ```c __syncwarp()``` o scheduling indipendente (unico PC per tutto il warp), l'hardware avrebbe probabilmente eseguito tutto il blocco ``` if``` ($A$ e $B$) prima di toccare il blocco ``` else``` ($X$ e $Y$). diff --git a/chapters/Lezione4.typ b/chapters/Lezione4.typ index 341269b..b015fd8 100644 --- a/chapters/Lezione4.typ +++ b/chapters/Lezione4.typ @@ -6,7 +6,7 @@ La *reduction* è un operazione che va a ridurre con un operazione associativa ( $ (x_1, dots, x_n) -> s = sum_(i=1)^(n) x_i $ -#nota()[ +#note()[ La somma può essere sostituita da altre operazioni associative come il prodotto, il massimo, il minimo ecc. ] @@ -19,7 +19,7 @@ Un approccio parallelo potrebbe sfruttare le seguenti idee: - Il numero di thread attivi vengono dimezzati ad ogni passo. - Occorre sincronizzare il lavoro dei thread ad ogni passo. -#attenzione()[ +#warning()[ L'array di input deve avere una dimensione pari ad una potenza di $2$ (es. $2^k$). In caso contrario, occorre inserire degli elementi di *padding* (zero) fino a raggiungere la dimensione successiva pari ad una potenza di $2$. ] @@ -54,7 +54,7 @@ def blockParReduce(array, out): # out ha dim = num_blocchi // TODO: if idx >= n: return possibile deadlock? -#nota()[ +#note()[ Serve *sincronizzazione* tra uno step e il successivo. Ogni step richiede che i risultati del passo precedente siano stati elaborati. Inoltre, è l'host che si occuperà di riunire i risultati parziali di ogni blocco (fuori dal kernel). @@ -260,7 +260,7 @@ La *$mg("soluzione")$* consiste nel *sequential addressing*, ovvero nel riassegn ], ) -#informalmente()[ +#informally()[ L'idea è che cambiare come vengono associati i thread agli indici della struttura dati. ] @@ -293,7 +293,7 @@ $ b = [a, (a_0 xor a_1), dots, (a_0 xor a_1 xor dots xor a_(n-1))] $ -#nota()[ +#note()[ Un'operatore $xor$ di scan deve avere le seguenti caratteristiche: - *commutativo*: $a xor b = b xor a$ - *associativo*: $(a xor b) xor c = a xor (b xor c)$ @@ -303,7 +303,7 @@ $ === Scan su big data -#informalmente()[ +#informally()[ In caso di un array di grandi dimensioni può essere necessario *dividere l'array in blocchi più piccoli*, eseguire la scan su ciascun blocco in parallelo, e poi combinare i risultati dei blocchi per ottenere il risultato finale. ] @@ -410,7 +410,7 @@ $ Passaggi: 1. Ogni blocco calcola una *scan locale* e produce un *valore di somma parziale* (l'ultimo elemento della scan locale). Il risultato è che ogni blocco ha i numeri progressivi corretti al suo interno, ma non tiene conto delle somme dei blocchi precedenti. - #nota()[ + #note()[ é importante che i blocchi in cui viene partizionato l'array stiano in *shared memory* per massimizzare le prestazioni. ] @@ -651,7 +651,7 @@ Utilizzando questa versione, il numero di operazioni è stato ridotto a $2(n-1) Le *operazioni atomiche* sono operazioni di lettura/scrittura su una variabile condivisa che vengono eseguite in modo *indivisibile*. Ciò significa che una volta iniziata un'operazione atomica, nessun altro thread può interferire con essa fino a quando non è stata completata. Questa indivisibilità viene garantita dall'hardware, il quale assicura che le operazioni concorrenti vengano serializzate. -#nota()[ +#note()[ L'ordine di esecuzione tuttavia è imprevedibile. ] @@ -691,6 +691,6 @@ Numba, mette a disposizioni le operazioni atomiche attraverso il pacchetto ``` n ), caption: [Operazioni atomiche disponibili in Numba CUDA], ) -#esempio()[ +#example()[ Un esempio di utilizzo può essere l'istogramma di un testo: contare le occorrenze di ogni lettera. ] diff --git a/chapters/Lezione6.typ b/chapters/Lezione6.typ index f6205b0..7351614 100644 --- a/chapters/Lezione6.typ +++ b/chapters/Lezione6.typ @@ -58,7 +58,7 @@ Uno dei vantaggi è la concurrency overlap. Tramite stream indipendenti abbiamo ) } -#attenzione()[ +#warning()[ Il DMA è unico, non posso fare trasferimenti paralleli ma posso far lavorare contemporaneamente CPU e DMA. Bisogna anche fare attenzione a limitare le allocazioni di pinned memory, siccome non può essere spostata può influire sul tutto il sistema. ] @@ -80,7 +80,7 @@ Il default stream si comporta così: - Se si lancia un kernel nel default stream aspetterà che tutti gli altri stream finiscano. - Se si lancia un kernel in un altro stream aspetterò che il default finisca. -#attenzione()[ +#warning()[ Possiamo avere due comportamenti diversi per lo stream di default: - `--default-stream legacy (or noflag)` lo stream si sincronizza con gli altri stream. È il comportamento descritto in precedenza. Da qui in avanti per il default stream si assume questo comportamento. - `--default-stream per-thread` lo stream non si sincronizza con gli altri. @@ -92,7 +92,7 @@ Gli stream possono essere usati per aumentare l'occupancy: Ad esempio se il nost //TODO Mettere immagine old vs new -#attenzione()[ +#warning()[ La creazione di uno stream deve avvenire prima del suo utilizzo. ] diff --git a/main.typ b/main.typ index ce1bdc9..3d427e1 100644 --- a/main.typ +++ b/main.typ @@ -1,37 +1,43 @@ -// Example usage of the academic-notes template -// This demonstrates how to use the template for academic notes - #import "template.typ": * #show: academic-notes.with( - // Required parameters + // --- Required title: "GPU Computing", subtitle: "Unimi - Master's Degree in Computer Science", authors: ( ("Luca Corradini", "LucaCorra02"), ("Matteo Zagheno", "zgn"), ), + lang: "it", - // Optional parameters (these have defaults if not specified) + // --- Optional, uncomment to change repo-url: "https://github.com/LucaCorra02/GPU-Computing", - license: "CC-BY-4.0", - license-url: "https://creativecommons.org/licenses/by/4.0/", - last-modified-label: "Last modified", // Customize this (e.g., "Ultima modifica" for Italian) - - // Custom introduction (optional - if omitted, a default one is generated) - introduction: [ - #show link: underline - ], + course-url: "https://myariel.unimi.it/course/view.php?id=2942", + year: "2025/26", + lecturer: "Giuliano Grossi", + // date: datetime.today(), + // license: "CC-BY-4.0", + // license-url: "https://creativecommons.org/licenses/by/4.0/", + // heading-numbering: "1.1.", + // equation-numbering: none, + // page-numbering: "1", - // Styling options (optional - these have sensible defaults) - heading-numbering: "1.1.", - figure-supplement: "Figure", + // --- Optional with language-based defaults, uncomment to change + // introduction: auto, + // last-modified-label: auto, + // outline-title: auto, + // part-label: auto, + // note-title: auto, + // warning-title: auto, + // informally-title: auto, + // example-title: auto, + // proof-title: auto, + // theorem-title: auto, + // theorem-label: auto, + // equation-supplement: auto, + // figure-supplement: auto, ) -// ============================================================================ -// YOUR CONTENT STARTS HERE -// ============================================================================ -// #part("Cuda Numba") #include "chapters/Lezione1-Introduzione.typ" #include "chapters/Lezione2-Gerarchia-Thread.typ" diff --git a/template.typ b/template.typ index 79b964b..4438ac9 100644 --- a/template.typ +++ b/template.typ @@ -11,6 +11,42 @@ #import "@preview/codly:1.3.0" #import "@preview/pinit:0.2.2" +// ============================================================================ +// LANGUAGE DEFAULTS +// ============================================================================ +// Pass lang: "en" or lang: "it" to academic-notes to select one. +// Any individual field can still be overridden independently. + +#let lang-en = ( + last-modified-label: "Last modified", + outline-title: "Table of Contents", + part-label: "Part", + note-title: "Note", + warning-title: "Warning", + informally-title: "Informally", + example-title: "Example", + proof-title: "Proof", + theorem-title: "Theorem", + theorem-label: "THM", + figure-supplement: "Figure", + equation-supplement: "EQ", +) + +#let lang-it = ( + last-modified-label: "Ultima modifica", + outline-title: "Indice", + part-label: "Parte", + note-title: "Nota", + warning-title: "Attenzione", + informally-title: "Informalmente", + example-title: "Esempio", + proof-title: "Dimostrazione", + theorem-title: "Teorema", + theorem-label: "THM", + figure-supplement: "Figura", + equation-supplement: "EQ", +) + // ============================================================================ // COLORED MATH TEXT HELPERS // ============================================================================ @@ -24,30 +60,50 @@ #let mp(body) = text(fill: purple, $#body$) // purple #let mb(body) = text(fill: blue, $#body$) // blue +#let comment(body) = text(size: 8pt, "(" + body + ")") // small comment in equations + // ============================================================================ // COLORED INFO BOXES // ============================================================================ -// Helper functions for different types of information boxes -// Note: default titles are in Italian, but can be customized - -#let nota(body) = { gentle-clues.info(title: "Nota")[#body] } -#let attenzione(body) = { gentle-clues.warning(title: "Attenzione")[#body] } -#let informalmente(body) = { gentle-clues.idea(title: "Informalmente", accent-color: green)[#body] } -#let esempio(body) = { gentle-clues.experiment(title: "Esempio", accent-color: purple)[#body] } - -// Proof box with auto-numbered equations -#let dimostrazione(body) = { +// Helper functions for different types of information boxes. +// Titles default to the active language; pass title: "..." to override per call. + +#let _note-title = state("note-title", "Note") +#let _warning-title = state("warning-title", "Warning") +#let _informally-title = state("informally-title", "Informally") +#let _example-title = state("example-title", "Example") +#let _proof-title = state("proof-title", "Proof") +#let _theorem-title = state("theorem-title", "Theorem") +#let _theorem-label = state("theorem-label", "THM") + +#let note(title: auto, body) = { + let t = if title != auto { title } else { context _note-title.get() } + gentle-clues.info(title: t)[#body] +} +#let warning(title: auto, body) = { + let t = if title != auto { title } else { context _warning-title.get() } + gentle-clues.warning(title: t)[#body] +} +#let informally(title: auto, body) = { + let t = if title != auto { title } else { context _informally-title.get() } + gentle-clues.idea(title: t, accent-color: green)[#body] +} +#let example(title: auto, body) = { + let t = if title != auto { title } else { context _example-title.get() } + gentle-clues.experiment(title: t, accent-color: purple)[#body] +} +#let proof(title: auto, body) = { + let t = if title != auto { title } else { context _proof-title.get() } set math.equation(numbering: "(1.1)", supplement: "EQ") - gentle-clues.memo(title: "Dimostrazione")[#body] + gentle-clues.memo(title: t)[#body] } - -// Theorem box with auto-numbering and auto-numbered equations -#let teoremi-counter = counter("teorema") -#let teorema(title, body) = { +#let theorems-counter = counter("theorem") +#let theorem(title: auto, body) = { + let t = if title != auto { title } else { context _theorem-title.get() } set math.equation(numbering: "(1.1)", supplement: "EQ") - teoremi-counter.step() + theorems-counter.step() gentle-clues.task( - title: title + " " + emph("(THM " + context (teoremi-counter.display()) + ")"), + title: t + " " + emph("(" + context (_theorem-label.get()) + " " + context (theorems-counter.display()) + ")"), accent-color: eastern, )[#body] } @@ -58,8 +114,10 @@ // Helper functions for creating cross-references // Link to a theorem by label -#let link-teorema(label) = { - underline(link(label, "THM " + context (1 + teoremi-counter.at(locate(label)).first()))) +#let link-theorem(label) = { + underline(link(label, context (_theorem-label.get()) + + " " + + context (str(1 + theorems-counter.at(locate(label)).first())))) } // Link to a section by label @@ -82,7 +140,7 @@ // FRONTMATTER (COVER PAGE AND OUTLINE) // ============================================================================ -#let frontmatter(title, subtitle, authors, introduction, date, last-modified-label) = { +#let frontmatter(title, subtitle, authors, introduction, date, last-modified-label, outline-title) = { align(center + horizon, block(width: 90%)[ #text(3em)[*#title*] #block(above: 1.5em)[#text(1.3em)[#subtitle]] @@ -109,7 +167,7 @@ } outline( - title: "Table of Contents", + title: outline-title, indent: auto, ) } @@ -120,17 +178,26 @@ // Create major divisions in the document (displayed as full pages) // Parts are numbered with Roman numerals (I, II, III, ...) +#let _part-label = state("part-label", "Part") #let part-counter = counter("part") -#let part(title) = { +#let part(title, reset-chapters: false, chapters-numbering: auto) = { part-counter.step() align(center + horizon)[ #context { let part-num = numbering("I", part-counter.get().first()) + let label = _part-label.get() heading(level: 1, numbering: none, outlined: true, bookmarked: true)[ - Part #part-num: #title + #label #part-num: #title ] } ] + show: rest => { + set heading(numbering: chapters-numbering) if (chapters-numbering != auto) + rest + } + if reset-chapters { + counter(heading).update(0) + } } // ============================================================================ @@ -149,21 +216,35 @@ #let academic-notes( // Required parameters - title: none, - subtitle: none, - authors: (), + title: str, + subtitle: str, + authors: array, + lang: str, // "en" or "it" // Optional parameters - introduction: none, - date: datetime.today(), repo-url: none, + course-url: none, + year: none, + lecturer: none, + date: datetime.today(), license: "CC-BY-4.0", license-url: "https://creativecommons.org/licenses/by/4.0/", - last-modified-label: "Last modified", - // Styling options heading-numbering: "1.1.", equation-numbering: none, - figure-supplement: "Figure", page-numbering: "1", + // Optional parameters with language default + introduction: auto, + last-modified-label: auto, + outline-title: auto, + part-label: auto, + note-title: auto, + warning-title: auto, + informally-title: auto, + example-title: auto, + proof-title: auto, + theorem-title: auto, + theorem-label: auto, + equation-supplement: auto, + figure-supplement: auto, // Content body, ) = { @@ -174,6 +255,36 @@ assert(title != none, message: "title is required") assert(subtitle != none, message: "subtitle is required") assert(authors.len() > 0, message: "at least one author is required") + assert(("it", "en").contains(lang), message: "at least one author is required") + + // ============================================================================ + // RESOLVE LANGUAGE DEFAULTS + // ============================================================================ + + let defaults = if lang == "it" { lang-it } else { lang-en } + let resolve(value, key) = if value == auto { defaults.at(key) } else { value } + + let final-last-modified-label = resolve(last-modified-label, "last-modified-label") + let final-outline-title = resolve(outline-title, "outline-title") + let final-part-label = resolve(part-label, "part-label") + let final-note-title = resolve(note-title, "note-title") + let final-warning-title = resolve(warning-title, "warning-title") + let final-informally-title = resolve(informally-title, "informally-title") + let final-example-title = resolve(example-title, "example-title") + let final-proof-title = resolve(proof-title, "proof-title") + let final-theorem-title = resolve(theorem-title, "theorem-title") + let final-theorem-label = resolve(theorem-label, "theorem-label") + let final-figure-supplement = resolve(figure-supplement, "figure-supplement") + let final-equation-supplement = resolve(equation-supplement, "equation-supplement") + + _part-label.update(final-part-label) + _note-title.update(final-note-title) + _warning-title.update(final-warning-title) + _informally-title.update(final-informally-title) + _example-title.update(final-example-title) + _proof-title.update(final-proof-title) + _theorem-title.update(final-theorem-title) + _theorem-label.update(final-theorem-label) // ============================================================================ // DOCUMENT METADATA @@ -188,13 +299,11 @@ // DEFAULT INTRODUCTION IF NOT PROVIDED // ============================================================================ - let default-introduction = [ + let intro-en = [ #show link: underline = #title - #if subtitle != none [ - #subtitle - ] + Notes from the #if course-url != none [#link(course-url)[#emph[#title]]] else [#emph[#title]] course#if year != none [ (a.y. #year)]#if lecturer != none [, taught by Prof. #lecturer]#if subtitle != none [, #subtitle]. Created by #(authors.map(author => [#link("https://github.com/" + author.at(1))[#text(author.at(0))]]).join([, ]))#if repo-url != none [, with contributions from #link(repo-url + "/graphs/contributors")[other contributors]]. @@ -203,16 +312,33 @@ Contributions and corrections are welcome via Issues or Pull Requests. ] - #last-modified-label: #date.display("[day]/[month]/[year]"). + #final-last-modified-label: #date.display("[day]/[month]/[year]"). + ] + + let intro-it = [ + #show link: underline + = #title + + Appunti del corso di #if course-url != none [#link(course-url)[#emph[#title]]] else [#emph[#title]]#if year != none [ (a.a. #year)]#if lecturer != none [, tenuto dal Prof. #lecturer]#if subtitle != none [, #subtitle]. + + Realizzati da #(authors.map(author => [#link("https://github.com/" + author.at(1))[#text(author.at(0))]]).join([, ]))#if repo-url != none [, con il contributo di #link(repo-url + "/graphs/contributors")[altri collaboratori]]. + + #if repo-url != none [ + Questi appunti sono open source: #link(repo-url)[#repo-url.split("://").at(1)] con licenza #link(license-url)[#license]. + Le contribuzioni e correzioni sono ben accette attraverso Issues o Pull Requests. + ] + + #final-last-modified-label: #date.display("[day]/[month]/[year]"). ] - let final-introduction = if introduction != none { introduction } else { default-introduction } + let default-introduction = if lang == "it" { intro-it } else { intro-en } + let final-introduction = if introduction == auto { default-introduction } else { introduction } // ============================================================================ // FRONTMATTER // ============================================================================ - frontmatter(title, subtitle, authors, final-introduction, date, last-modified-label) + frontmatter(title, subtitle, authors, final-introduction, date, final-last-modified-label, final-outline-title) // ============================================================================ // GENERAL SETTINGS @@ -220,12 +346,12 @@ set terms(separator: [: ]) set heading(numbering: heading-numbering) - set math.equation(numbering: equation-numbering, supplement: "EQ") + set math.equation(numbering: equation-numbering, supplement: final-equation-supplement) show: equate.equate.with(breakable: true, sub-numbering: true) show: gentle-clues.gentle-clues.with(breakable: true) show: codly.codly-init.with() show link: underline - set figure(supplement: figure-supplement) + set figure(supplement: final-figure-supplement) // ============================================================================ // PAGE BREAK EVERY CHAPTER