diff --git a/notebooks/Phase1-1.5_summary.ipynb b/notebooks/Phase1-1.5_summary.ipynb new file mode 100644 index 0000000..ba0bca9 --- /dev/null +++ b/notebooks/Phase1-1.5_summary.ipynb @@ -0,0 +1,659 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5d03c826", + "metadata": {}, + "source": [ + "# Phase 1 and Phase 1.5 Summary" + ] + }, + { + "cell_type": "markdown", + "id": "2e637cdf", + "metadata": {}, + "source": [ + "In Phase 1, a rule-based MIDI Evaluation Pipeline is developed to capture Musical Instrument Digital Interface (MIDI) sequence and compare students’ response against reference. Phase 1.5 extends the functionality so that the pipeline can evaluate not only melody notes (one note at a time) but also block chords (multiple notes played simultaneously).\n", + "\n", + "The personalised, actionable, formative feedback messages are generated across the core dimensions of music (pitch accuracy, timing deviation, and note duration).\n", + "\n", + "The general structure of the designed pipeline:\n", + "- Step 0: normalize the start times of the MIDI sequences to make sure the first note starts at t = 0.0, then group simultaneous notes into chords.\n", + "- Step 1: edit-distance alignment, to identify missing/extra/wrong pitch in the response sequence.\n", + "- Step 2: estimate the overall timing and duration difference between reference and response, using linear regression.\n", + "- Step 3: compute metrics to provide event-level feedback (note/chord-level feedback)\n", + "- Step 4: compute the summary statistics to provide overview feedback\n", + "- Step 5: generate human-readable feedback message from step 3 and step 4\n", + "- Step 6: provide an additional overall pass/fail judgement based on step 3 and step 4" + ] + }, + { + "cell_type": "markdown", + "id": "2fdb8738", + "metadata": {}, + "source": [ + "##### set up the notebook" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "89c6fb89", + "metadata": {}, + "outputs": [], + "source": [ + "referenceMIDI = {\n", + " \"performance_type\": \"reference\",\n", + " \"notes\": [\n", + " {\"pitch\": 60, \"start\": 0.00, \"duration\": 0.40}, # C4 — single note\n", + " {\"pitch\": 62, \"start\": 0.50, \"duration\": 0.40}, # D4 — single note\n", + " {\"pitch\": 64, \"start\": 1.00, \"duration\": 0.40}, # E4 — single note\n", + " # Chord 1: C major (C4, E4, G4) — all three notes start together\n", + " {\"pitch\": 60, \"start\": 1.50, \"duration\": 0.40},\n", + " {\"pitch\": 64, \"start\": 1.50, \"duration\": 0.40},\n", + " {\"pitch\": 67, \"start\": 1.50, \"duration\": 0.40},\n", + " {\"pitch\": 69, \"start\": 2.20, \"duration\": 0.40}, # A4 — single note\n", + " # Chord 2: A minor (A4, C5, E5) — all three notes start together\n", + " {\"pitch\": 69, \"start\": 2.80, \"duration\": 0.40},\n", + " {\"pitch\": 72, \"start\": 2.80, \"duration\": 0.40},\n", + " {\"pitch\": 76, \"start\": 2.80, \"duration\": 0.40},\n", + " {\"pitch\": 71, \"start\": 3.50, \"duration\": 0.40}, # B4 — single note\n", + " ]\n", + "}\n", + "\n", + "responseMIDI = {\n", + " \"performance_type\": \"response\",\n", + " \"notes\": [\n", + " {\"pitch\": 60, \"start\": 0.00, \"duration\": 0.48}, # C4 — correct\n", + " {\"pitch\": 63, \"start\": 0.60, \"duration\": 0.48}, # D#4 — wrong pitch (expected D4)\n", + " {\"pitch\": 64, \"start\": 1.00, \"duration\": 0.48}, # E4 — local timing error\n", + " # Chord 1: student plays C major correctly (all 3 notes, within 50ms window)\n", + " {\"pitch\": 60, \"start\": 1.80, \"duration\": 0.48},\n", + " {\"pitch\": 64, \"start\": 1.81, \"duration\": 0.48}, # slight onset jitter, still grouped\n", + " {\"pitch\": 67, \"start\": 1.82, \"duration\": 0.48},\n", + " {\"pitch\": 69, \"start\": 2.64, \"duration\": 0.48}, # A4 — correct\n", + " # Chord 2: student plays A minor imperfectly — missing E5 (76), plays wrong note F5 (77)\n", + " {\"pitch\": 69, \"start\": 3.36, \"duration\": 0.48},\n", + " {\"pitch\": 72, \"start\": 3.36, \"duration\": 0.48},\n", + " {\"pitch\": 77, \"start\": 3.37, \"duration\": 0.48}, # F5 instead of E5\n", + " {\"pitch\": 71, \"start\": 4.20, \"duration\": 0.48}, # B4 — correct but duration error\n", + " ]\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0d95f9de", + "metadata": {}, + "outputs": [], + "source": [ + "# Import\n", + "from evaluation_function.compare_MIDI import (\n", + " normalize_start_times,\n", + " group_notes_into_events,\n", + " event_alignment_ED,\n", + " estimate_global_timing,\n", + " estimate_global_duration_scale,\n", + " event_level_feedback,\n", + " compute_stats,\n", + " generate_feedback_message,\n", + " compare_performance_ED,\n", + ")\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.patches as mpatches" + ] + }, + { + "cell_type": "markdown", + "id": "3c263220", + "metadata": {}, + "source": [ + "Note: all parameters are defined with a reasonable default value in the code, and all of them can be set by the teachers manually." + ] + }, + { + "cell_type": "markdown", + "id": "52f688cc", + "metadata": {}, + "source": [ + "### Step 0" + ] + }, + { + "cell_type": "markdown", + "id": "ff1c2fc2", + "metadata": {}, + "source": [ + "**Normalize start times**: Adjust the start time of the first note to t=0 for both the reference and the response MIDI. This action will help eliminate the time gap between student start recording their practice and start playing the first note (if the gap exists)." + ] + }, + { + "cell_type": "markdown", + "id": "69bed847", + "metadata": {}, + "source": [ + "**Group simultaneous notes into chords**: Notes which start within a 50 ms (default) window are grouped into a block chord event; all others become single-note events.\n", + "- Limitation and Possible future work: handle broken chords\n", + "\n", + "A \"chord\" = multiple(>=3) notes with the same or very close start times\n", + "- Block Chords: multiple(>=3) notes with the same start time\n", + "- Broken Chords / Arpeggios: multiple(>=3) notes play one after another, in a given order\n", + "focus on block chords first. " + ] + }, + { + "cell_type": "markdown", + "id": "4f7ca163", + "metadata": {}, + "source": [ + "#### Result" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "755b8617", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Normalised response notes:\n", + " {'pitch': 60, 'start': 0.0, 'duration': 0.48}\n", + " {'pitch': 63, 'start': 0.6, 'duration': 0.48}\n", + " {'pitch': 64, 'start': 1.0, 'duration': 0.48}\n", + " {'pitch': 60, 'start': 1.8, 'duration': 0.48}\n", + " {'pitch': 64, 'start': 1.81, 'duration': 0.48}\n", + " {'pitch': 67, 'start': 1.82, 'duration': 0.48}\n", + " {'pitch': 69, 'start': 2.64, 'duration': 0.48}\n", + " {'pitch': 69, 'start': 3.36, 'duration': 0.48}\n", + " {'pitch': 72, 'start': 3.36, 'duration': 0.48}\n", + " {'pitch': 77, 'start': 3.37, 'duration': 0.48}\n", + " {'pitch': 71, 'start': 4.2, 'duration': 0.48}\n", + "\n", + "Normalised reference notes:\n", + " {'pitch': 60, 'start': 0.0, 'duration': 0.4}\n", + " {'pitch': 62, 'start': 0.5, 'duration': 0.4}\n", + " {'pitch': 64, 'start': 1.0, 'duration': 0.4}\n", + " {'pitch': 60, 'start': 1.5, 'duration': 0.4}\n", + " {'pitch': 64, 'start': 1.5, 'duration': 0.4}\n", + " {'pitch': 67, 'start': 1.5, 'duration': 0.4}\n", + " {'pitch': 69, 'start': 2.2, 'duration': 0.4}\n", + " {'pitch': 69, 'start': 2.8, 'duration': 0.4}\n", + " {'pitch': 72, 'start': 2.8, 'duration': 0.4}\n", + " {'pitch': 76, 'start': 2.8, 'duration': 0.4}\n", + " {'pitch': 71, 'start': 3.5, 'duration': 0.4}\n" + ] + } + ], + "source": [ + "# Step 0a: Normalise start times\n", + "response_notes = normalize_start_times(responseMIDI[\"notes\"])\n", + "ref_notes = normalize_start_times(referenceMIDI[\"notes\"])\n", + "\n", + "print(\"Normalised response notes:\")\n", + "for n in response_notes:\n", + " print(\" \", n)\n", + "\n", + "print()\n", + "print(\"Normalised reference notes:\")\n", + "for n in ref_notes:\n", + " print(\" \", n)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "97cb4574", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Response events:\n", + " [1] event_type=note event_start=0.00 event_duration=0.48 notes=[{'pitch': 60, 'start': 0.0, 'duration': 0.48}]\n", + " [2] event_type=note event_start=0.60 event_duration=0.48 notes=[{'pitch': 63, 'start': 0.6, 'duration': 0.48}]\n", + " [3] event_type=note event_start=1.00 event_duration=0.48 notes=[{'pitch': 64, 'start': 1.0, 'duration': 0.48}]\n", + " [4] event_type=chord event_start=1.80 event_duration=0.48 notes=[{'pitch': 60, 'start': 1.8, 'duration': 0.48}, {'pitch': 64, 'start': 1.81, 'duration': 0.48}, {'pitch': 67, 'start': 1.82, 'duration': 0.48}]\n", + " [5] event_type=note event_start=2.64 event_duration=0.48 notes=[{'pitch': 69, 'start': 2.64, 'duration': 0.48}]\n", + " [6] event_type=chord event_start=3.36 event_duration=0.48 notes=[{'pitch': 69, 'start': 3.36, 'duration': 0.48}, {'pitch': 72, 'start': 3.36, 'duration': 0.48}, {'pitch': 77, 'start': 3.37, 'duration': 0.48}]\n", + " [7] event_type=note event_start=4.20 event_duration=0.48 notes=[{'pitch': 71, 'start': 4.2, 'duration': 0.48}]\n", + "\n", + "Reference events:\n", + " [1] event_type=note event_start=0.00 event_duration=0.40 notes=[{'pitch': 60, 'start': 0.0, 'duration': 0.4}]\n", + " [2] event_type=note event_start=0.50 event_duration=0.40 notes=[{'pitch': 62, 'start': 0.5, 'duration': 0.4}]\n", + " [3] event_type=note event_start=1.00 event_duration=0.40 notes=[{'pitch': 64, 'start': 1.0, 'duration': 0.4}]\n", + " [4] event_type=chord event_start=1.50 event_duration=0.40 notes=[{'pitch': 60, 'start': 1.5, 'duration': 0.4}, {'pitch': 64, 'start': 1.5, 'duration': 0.4}, {'pitch': 67, 'start': 1.5, 'duration': 0.4}]\n", + " [5] event_type=note event_start=2.20 event_duration=0.40 notes=[{'pitch': 69, 'start': 2.2, 'duration': 0.4}]\n", + " [6] event_type=chord event_start=2.80 event_duration=0.40 notes=[{'pitch': 69, 'start': 2.8, 'duration': 0.4}, {'pitch': 72, 'start': 2.8, 'duration': 0.4}, {'pitch': 76, 'start': 2.8, 'duration': 0.4}]\n", + " [7] event_type=note event_start=3.50 event_duration=0.40 notes=[{'pitch': 71, 'start': 3.5, 'duration': 0.4}]\n" + ] + } + ], + "source": [ + "# Step 0b: Group into events\n", + "# chord_onset_window=0.05 means notes within 50 ms are grouped as a chord.\n", + "response_events = group_notes_into_events(response_notes, chord_onset_window=0.05)\n", + "ref_events = group_notes_into_events(ref_notes, chord_onset_window=0.05)\n", + "\n", + "print(\"Response events:\")\n", + "for i, e in enumerate(response_events):\n", + " print(f\" [{i+1}] event_type={e['event_type']} event_start={e['event_start']:.2f} event_duration={e['event_duration']:.2f} notes={e['notes']}\")\n", + "\n", + "print()\n", + "print(\"Reference events:\")\n", + "for i, e in enumerate(ref_events):\n", + " print(f\" [{i+1}] event_type={e['event_type']} event_start={e['event_start']:.2f} event_duration={e['event_duration']:.2f} notes={e['notes']}\")" + ] + }, + { + "cell_type": "markdown", + "id": "bf6ecc82", + "metadata": {}, + "source": [ + "### Step 1" + ] + }, + { + "cell_type": "markdown", + "id": "60aee2a3", + "metadata": {}, + "source": [ + "The main purpose of note alignment here is to identify if there is any missing/extra notes and notes with wrong pitch.\n", + "\n", + "Based on the findings during the project plan phase, Dynamic Time Warping (DTW) is commonly used for alignment. The algorithm can be found in this book (Chpater 3.2 Dynamic Time Warping):\n", + "M. Müller, Fundamentals of Music Processing. Cham: Springer International Publishing, 2021, ISBN: 9783030698072. DOI:https://doi.org/10.1007/978-3-030-69808-9.\n", + "\n", + "In general, this DTW algorithm finds an optimal possibly nonlinear alignment between response MIDI sequence to reference MIDI sequence.\n", + "\n", + "Basic approach:\n", + "- Evaluating the local cost measure for each pair of elements in the response(X) and reference(Y) sequences. \n", + "- Dynamic programming to find an alignment path between X and Y having minimal overall cost, i.e. DTW distance. The algorithm computes a cumulative distance path, the timestamps of the target MIDI are warped so they perfectly align with the anchor points of the reference MIDI.\n", + "\n", + "However, this basic approach will not correctly handle the missing note case as expected, because it allows a note to match with multiple notes, and each note must be paired. Let's say, there is a note missing in the response, this algorithm tends to match a response note with two reference note, instead of reporting the missing problem." + ] + }, + { + "cell_type": "markdown", + "id": "83034a6a", + "metadata": {}, + "source": [ + "**Edit-distance alignment**\n", + "\n", + "A simplified version of (Mongeau, M., Sankoff, D. Comparison of musical sequences. Comput Hum 24, 161–175 (1990). https://doi.org/10.1007/BF00117340) is applied.\n", + "\n", + "The Edit-distance approach allows a note to be explicitly left unaligned (i.e. insertions (extra notes) and deletions (missing notes)) at the cost of gap_penalty. The gap penalty is empirically set to 6. This value is chosen to encourage the alignment algorithm to interpret most pitch deviations as replacement (wrong pitches) rather than decomposing them into separate insertion and deletion operations. Such behaviour is more consistent with common music teaching practice, where a note played at the correct temporal position but with an incorrect pitch is typically regarded as a wrong note instead of a missing note accompanied by an extra note.\n", + "\n", + "The reference note sequence and the student's response sequence are aligned using dynamic programming.\n", + "\n", + "The accumulated cost matrix is constructed with cost functions:\n", + "- Note vs note: absolute pitch difference in semitones, so that matched note pairs have zero cost while larger pitch deviations have proportionally higher penalties.\n", + "- Chord vs chord: Hamming distance on 12-dim pitch-class binary vectors.\n", + " - There is a paper suggesting that considering chord as unordered set of pitch classes: (Rocher, Thomas & Robine, Matthias & Hanna, Pierre & Desainte-Catherine, Myriam. (2010). A Survey Of Chord Distances With Comparison For Chord Analysis). \n", + "- Type mismatch (note vs chord): gap_penalty (forces the aligner to prefer leaving them unmatched).\n", + "- Gap (missing/extra event): gap_penalty.\n", + "\n", + "Then, backtracking is performed to recover the optimal alignment path, Each aligned pair or unmatched element is classified into one of the four operation types according to the moving direction during backtracking:\n", + " - diagonal = match (identical pitch)/replacement (wrong pitch)\n", + " - vertical = extra (additional note played)\n", + " - horizontal = missing (note not played)" + ] + }, + { + "cell_type": "markdown", + "id": "9d949081", + "metadata": {}, + "source": [ + "#### visualisation of the alignment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c9802ed0", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAvsAAAMWCAYAAABvLEmmAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAjpNJREFUeJzt3Qd4FNX+xvHf0kE6qFSpgooFuxR7QxBQrFwRARFRLNeK9Yp6saIXsRdUrICiFHujCTbEgiBFAQFBRbpCaNn/8x6Z/W9CIJvNJjsz+/08z0K2JJmcTGbfOed3zkSi0WjUAAAAAIROiXRvAAAAAICiQdgHAAAAQoqwDwAAAIQUYR8AAAAIKcI+AAAAEFKEfQAAACCkCPsAAABASBH2AQAAgJAi7AMAAAAhRdgHAB8YPny4RSIR++GHH8xPPv30U7ddb731Vro3JSP4dT8AEFyEfQBp9ffff1vlypVdwBk3bhy/jSQ8//zzrv1mz56d9vZbvHixXX311daiRQurWLGi+93ut99+dtFFF9m0adN88fO/99577vW6Pfnkk3m+5oILLoi95q+//irybSoq8T+rbmXKlLFdd93VjjjiCLvuuuvSvn0Aih5hH0DaezIVpurUqWNPP/00v40A++CDD2zfffe1L7/80gYPHmxLly61ZcuW2aOPPmq//vqrHXrooeYnu+yyiwvluWl/HDVqlHu+uJ177rkWjUZdO6aS/rb0dbOysmzWrFl222232YwZM9yJ2IMPPpjS7wXAXwj7ANJKIeS4446zG264wd555x0XEBE8ixYtsjPPPNMOOuggGz9+vJ144omuV1+B+aijjnK/WwVMP+nSpYt9/vnnNmfOnByPjxw50jZv3mwdO3a0sClRooTr2T/llFNcr3/37t3tmmuucb8fAOFE2AeQNqpL/uKLL+zSSy91ZRPlypWz5557Ls/X/vbbb+51DRs2dK/ba6+9bODAga6nMtHX3HLLLa6UYcuWLTm+9oQJE9zjCj+eJ554wj32008/2fXXX2+77babVatWzfr16+c+XzeVQdSqVcsF2q5du9q6detyfN2CfL+8eK/zbhUqVHBhOr70ZMCAAdazZ0/38d577x177ejRo3O0S9++fa1evXqujGOPPfZwP1N824l65BXMy5cv71579913W6LUO6yf/4EHHrDSpUvn+Rptazz1MCtw16hRw/2+VPqjEQH1QHvWrFljV1xxhTVq1MhtV9OmTe2yyy6z33//PeGff0dOPvlkq1279na9+9oHO3Xq5LarKH4n8fvWzTff7Ea1dF+/j9w1+9p39DvR/qfREc+mTZtcKY4+12uLZNx7771un9D/AMKJsA8grb36devWdcFKvcDnnXeeDR06NEfYE4WcQw45xCZNmuSC2PLly23s2LEuHL3//vsJvyYZCm4qqZg3b569/vrr9sILL7jH1BuqcKpe4bffftv1jN50002WSsccc4xrC+/2yy+/WI8ePdwJx0svvRTbPu8E6ccff4y99rTTTosF/cMOO8y++uore+2112zlypX28ssvuzKV008/Pfa9Zs6caccee6yrs//+++/t22+/dY8nWuKhExcFUgXfRKhWXGH1zz//dL8zlftcfvnl1r9/f/fzeXr37u0mB6u3fdWqVfbRRx+534dX8pXfz78zJUuWtPPPP99efPFFy87Odo/p96xJyWrnovqdeG699VZ34qVyGq/GP7dSpUrZiBEjXI/8WWed5UYcRPMivv76a/fc7rvvbsmqWbOm+5199tln7gQCQAhFASANNmzYEK1evXr09ttvjz327bffKuVHP/jggxyv7dGjR7RcuXLRX375ZYdfL5HX3Hzzze7rb968Ocfj48ePd4+/++67sccef/xx99gtt9yS47V9+vSJVqhQIXrjjTfmePySSy5xj2dnZyf1/V599VX32IwZM6L5Oe2006Jt27aN3X/uuefc5/7444/bvfaiiy5y27VkyZIcj3/00Ufucz788EN3/6yzzopWrVo1unbt2hyvO/PMM93rxo0bt9NtUtsfcsgh0USdffbZ0V122SW6fPnyHI9fc8010UgkEp01a5a7X7Nmzejll1++06+1s58/L2p3vV5tru+jj9977z333E033RStXbt2dMuWLdF+/fq559atW5fS34m3b1111VXbPbej/eCTTz6JlixZMnrllVdGX3nlFfea+++/P+Gf9emnn97ha7zf8a+//prv1wMQPPTsA0gL9SyvXbvWrdLiOeCAA6x169b2zDPP5Hites3btm3rekF3JJHXJEO1zfFUGrR+/Xpr165djsdVrqHHC1NSkZt6gx9++GE7+OCDXY97fDmISkASoRWO1KYaQYl39NFHu3KbiRMnuvsff/yx69mvVKlSjtcl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mzdvtpUrV1okErFq1arlaD89/tdff7mP9Xz9+vVp2wLKysqypUuXuv22du3aVrZs2dhzf/75p/3+++85Xl+jRg2rVasW7ZwA7Z9z5syJ3d9nn32sSpUq2x0/FixYYGvWrLFGjRq59kVi/v77b5syZUqO9q1Xr952r5s3b54tW7bM9thjD2vYsCHNW0g//fST22d1HNhvv/1oz1TKyrJlzzxjM0aNsppZWXZogwZmnTrZ1i5dzMqVC3xbpzXsv/XWW+6Am5sOCi1btkzLNiEYFITWrl3rApJC6datW90bikK9ZGdn25IlS9zJgE4E9Jrq1au70Ir8zZ8/33788UerVKmSa7t169bZYYcdZnXq1HHPKyDpd6D2VbAi7BfMp59+atOnT7eaNWu6E1K1YadOnWzPPfd0z8+dO9e++uort097mjRpQthP0OrVq23atGkWjUbtiy++sJtvvjlH2F++fLndd9997uNdd93VnRicddZZ1q5duwL+JjPT+vXrbcKECe7jyZMn27///e8cYV8nsbfeeqs7dugk6vvvv3fHj//+979WsmTJNG55MOn97Morr7SpU6e6bPTDDz+4sP/444/TiZUCkXHjrG+3bvbp5s22j5n9aGYlv/vOPhw71upde61tevppy+7QwYIsrWH/5ZdfdmHBo48/+OADu+222wj72CkF+N122831KouCvcJnA52Nm9lvv/3mTgCaNm3q3lz0pq/eKCRGJ00KPt4b88yZM+2bb76JhX31hOr266+/2ueff06zFlDlypXtkksuib1Rjx8/3t555x33hu5R793pp59O2yahcePGdvnll7tRPYX9vN57FPKvvfZaN7KisD9w4EA78MADbffdd6fN86G2u+uuu9zHOknNa9TqxhtvdMdfb6TqtNNOcycGxxxzDO1bQKNGjXJt9+GHH7q2V+eLjs+vvvqqde/enfYshBJvvWWlzjnH2pvZ89seyzazI81soJk9vmaNlTn7bNs0YoRln3pqYNs6rTX72lFHjx4du5177rkuvPXo0SOdm4WAhCUv6EuZMmVc74co5KtnTwdF9ZrqwKgRpIoVK6Zxi4NFoT6+B05tl9coHJKz//775+iRU8BUQNJJqUf7rnr4Fy1a5D5G6qgUQsHeK/1r3ry5VahQwY0GIDUnW17QF42qqn3jO/eQuBkzZrgOUL2niUZcW7Vq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ChH0AQApD/tadeeTTz5J5lMBAClC0AcAFEkZT/Pmza1r16524YUXWtOmTS0SieR4/rLLLkvmywIAEkTQBwAUWdgfO3as1atXz63Io1tuhH0AKDoEfQBAkYb92bNnJ/NpAIBCIugDAIq8Zh8AUPwI+gCAYruo1urVq+2ZZ56xH3/80aLRqO2zzz7Wu3dvq1q1arJfEgCwAwR9AECx9ex//fXX1qRJE3vwwQft999/t+XLl7uPNVl3+vTpSW0IACBvBH0AQLH27F999dV29tln25AhQ6x06dKxtfevuOIKu+qqq2zixIlJbxAA4P8R9AEAxR72v/jiCxs1alQs6Is+vvPOO61+/fqF2qCwuO+++2zt2rXu4xYtWrilSuOtWrXK3nrrLfvll1+sbNmyduCBB9pxxx1nJUowjSIR99xzT2yi+B577GF33HFHjud//fVXGzlypM2bN8+qV69uHTt2tMMPPzxlv99Mc84559iGDRu2e1ylfLvttltatilMRrz4oj12111mK1aYbdlib7RtazXPPtts0yaLXHyxRaJR97rsfv0sOniwWa7ljrFz9957r02ZMmW7xy+99FJr164dzVdAv/32m91yyy2x++eff74dffTROV4zc+ZMGz16tHutcoGOIQ0aNKCtE5Cdne3ylEcXMT3ppJNyvOaPP/6wt99+27Wv3uM6dOjgVklEYv744w/75ptvbOXKlVa5cmU76KCDrHbt2rHnX3/9dfv7779zfM7pp5/uXpsxYX+XXXZxDVWzZs0cj2unq1ChQoF26OHDh7vlO//66y8Xivv27Wt16tSxoOvSpYsb7dDBTsEz3tatW+22225zIUl/oGvWrLGXXnrJnQCcddZZadvmIDnjjDPcPvPOO+/Yd999l+M5tbeWf9UfpgK+5pX069fPnSCccMIJadvmILvuuuvcfut57rnnbNasWQT9VBg3zo668krbY+1amx+J2AXRqG18910r8c47pojvxXqCfuGOx8ccc0zsvo4ZAwYMcHPNUHCam+ctsX3DDTfYn3/+uV2przoC2rdv7zqxPv74Y+vZs6e98sorVqtWLZo8H+r0O/fcc93HygYqlY6n9r7pppusTZs2LkPoWKz9eeDAgbb77rvTvvlYvHixy50tW7Z05ecLFy60559/3rp3725169Z1r1m6dKkddthhsftSkHwbirCvA6d6qh966CHXGF5vv8p49FyiLrjgAterrZ6VUqVKuYPD008/7Q4UQQ/82oFkwoQJ250d6qRIO9Ktt95qu+66q3tsxYoV9tVXXxH2E7Tnnnu6/3Vmnjvsq03Vq++NPLVu3doWLFjgTgwI+8k55JBDYh8r9F9yySWuVxSFNG6clejSxfR2oj65qtt68L2e/FjQb9+eHv0UHC88L7/8sgv/GhVEwZUrV84FJSlTpsx2z6vj7vHHH8/RMz158mT78ssvrVOnTjR5ghcvlfLly2/3nEql9T6nEyjZf//93Xucevp79epF++Zj9913dxeF9S4Iqzmo6iTUSVN8uFcbh6VaJamwr8m4F198sTtj10o8okbTmaieS9QjjzxiVapUid1XqUWlSpXsgw8+sB49elhY1ahRwypWrGg///yz25k0wqGPGzZsmO5NC4W83nyWLVvm/qBRePr7ZBQqBbKyrMS2N2sv3OfF9e5PnWrRjRuVslLxnTOaVpIbN26cPfHEE+nelFCfDOTuSdVIbKNGjdK2TWGitsy98mG1atVs7ty5adumIGeEaDRq69at265TQKV/6shWW6vMJ8ijUkmFfQVyDcepLEIlEgr6e++9d4HPgOKDvnz//fe2ZcsW97XCfiBUveP999/v6sL0h6taRu8sHan15ptv2pw5c+z222+naVPghRdesM6dO2/394uCibz+ukVWr87/dfpn9WqLjBpl0fPOo5kLSaN+CkaM8hU9lfpo7prCfv/+/W2//fYrhu8afgqlkyZNclUCCqDqfFF+8nqqUTATJ050na7eaJWceeaZbhR706ZNNn/+fFe6+q9//Suw806SXmdfNARa2GHQadOmuZo/HRA0DKXwu7OJlBs3bnQ3jzcJNkhU1vPwww+7A1/btm1d2H/ttdfcrVu3bunevFDRJGiNNqm9mbxUeJpQrtI0DRejcCJjxli0RAmLZGfn+1r3utGjCfsp8OKLL7o37ZIlS6biy2En+vTp4yb2qzRXZb8aXVWJDwpHZVHqaFXdvsK+cpBKh5csWULTFtCUKVNcKfB5552Xo2QqvpRc+632488//zzzwr4m1v7vf/9zO5xoopO3JGdBaFjPm+Dz7LPP2o033uh25B3V7N99992B76HVsJCGjLwJTt7ED42UqBRK8xdQeDp5UqnYY489Ro9SCoOS3qw1pIlCWrkyoaAvel10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/Pn28ccfu+P0nnvuaaeccoqVKVMmDVsbzGPtiBEjYvePPPJI9z6X+zWTJk2yP//80+rUqWPHHnuslStXLg1bG0zaL2fOnGlr16512WC//faLtd/WrVtdfsitY8eOadjSYNq6davLX9pPy5Yta3vssYdVqlQp9vx3331nGzdudB9Xq1bNHSOCLK1JKBqN2ttvv20333yz3Xbbbfbhhx9aWOy///52yCGH2NKlS23atGk5nvvhhx/sggsusN9++80aN27sdqrTTjvNfvnll7Rtb9DNmzfP2rRpY5999pk1b97cvv32W7vlllvSvVnhMG6cNb/mGjt2yhSrtGiRvfTbb2ZjxliJHj0s0qcPQT8FWrZsaa1bt47dSpUqZePGjXMdASi4qlWr2sEHH2wHHHCAe49ZsWJFjuf1XnPHHXdYJBKxunXr2siRI+3iiy+2LVu20NwJUDjSSahu33zzjS1btizH83pvu/76690JbP369W369Ol255132ubNm2nfBMyZM8d1XClsKujr5PWZZ56x9evXx7LT999/bzVr1nQh1bshMdFo1D766CPX6Vq5cmXXzh988IH98ccfsdeo3XfbbTfX5vGPB1Xaevazs7PdgVi9La1atXINqt7vzp0727PPPmtBd9JJJ7n/FTrXrVuX4zn1cujNpXz58u7+Oeec425jx461yy+/PC3bG3RXXXWVnXzyyfa///0v9tjvv/+e1m0KhXHjrESXLtbKzForRJnZ0yrdyc52T3v999nt29OjXwjad+PpWKBeOoVWFFytWrVc++lN/K677srz5OqEE05wYV9OPPFE17OvkJW7hxrbq1Chgh199NHu4zfeeGO753Wiqo6sSy+91N1Xr76O0ZMnT7bjjjuOJs2HTvIvuugiK1mypLt/4IEH2sMPP+z2T33sUcdW9erVac8kHHXUUbEMJjpWLFiwwAV8qVevnvtfHQVZWVmBb+O0hX0dZBV444eodUDQAfjKK690JwJhlfuPU22hHg+GOJOjXiX16F977bV2zz332OrVq92oyhlnnJGS31fGysqyEj17ug+9Mp286JnI1KkW1ZAnw/SFtnjxYpswYYIL/Cgau+66a4776uFTsNJwPQpP7akRE0/p0qVdiNKoNmE/f+ptzp0RVHaidoyn9z3tt+qFVjWBRlyQv0gkkiPoe2E/zH//aQ37uWtRvfsaAgxz2M9NJz06OKpuHwW3cOFC9/+NN97oevM0nDlw4EA3VP/kk0/SpEmKvP66RVavzv91+mf1aouMGmXR886jvQvpxRdftL322sudsKLoqBb6wQcfdKPK6jFVWY9GXVF4Kt1RmYnKolSSprpoylSTN3HiRBdOmzVr5u5rLtqpp54aC6mzZs1ywf/CCy+0XXbZhV04QT/99JPrudf8CJ1g7bPPPqFtO19N0FVNmnbUQw89dIev0Y7tTZrwDthBpnpSDc8NGTLEdt9993RvTiB5Q/Hdu3d3Exrl8MMPd6VUN910kzVo0CDNWxhMkTFjLFqiRKxkZ2fc60aPJuwXkkb4VKt7zTXXFPZLIR+ajKu6foV9lVq+8sorrqQ0fpIekqNOF81F69+/v+t8UYeMTqQU/FEwU6dOtRkzZth5550Xm0CusB/fIaqOgaFDh9qXX37pKiSQmMqVK7u2VFWFTkbV0awT1TDyzV/e+++/7ybwPPbYYzutQbv77rvt9ttvtzB4/fXXXdB/9NFH3RAckuOF+aZNm8Ye82bO64+XsJ+klSsTCvqi10VXrkz2O2EbrbDx999/U4JWDPQG761ecuaZZ9rpp5/uOl9yr5yGgtMJk+ZKzJ0715VVduvWzY2yMom0YFTOp6B//vnn7zQXKbCqs9BbARCJ2W233WI1+joeqK3DGvZL+GWH7tKliw0YMMBNStkZlWpoh/Zuqm8N6lC9Tmx0ACToF34ykyYtffLJJ7HHxo8f7/54vWFPJKF6dddjnwj3OiaKFdoLL7zgjoX0LhetKVOmuBpoj8pM1Lsf5prd4qT3ZnW0qDRXoyValUelUsccc0y6Ny0wq8VodRiV52jEOnfQV3v+9ddfsfvqINDkUk1MR/7Wrl2bo/1E98M85yHtPftah1e1ZzfccINbgjM/+mUE4Rfy0ksvuYObhjI1NH/rrbe6HubevXvbp59+ag888IBbEeLll1+OfY7W0T377LPTut1BpVV41DunM/MqVaq4VR+0djlv3snTOvol3nzTfTzezIaZ2SKVmmjd8m2veUwrc2zr2c8+7bSU/C4zla4PoSF7LUOMwlFv8uDBg92qb/L888+7FWJ0jNh3331dWYnq9Zs0aeLqyrV8pELp8ccfT9Mn6Omnn3YnTDpJ0vFW9c8qJ9FN5TrqyNJJ66ZNm1wQ7dOnT2yFE+yclir96quv3IpGykgejV57cxuVHdS+mrSrEhSNmuysBBo5S38///xzt5+qU1Anp9pPtXx3/HLe6gRQTb/2c5VIVaxYMbB1/ZGoTiHTRAcILXemuj6F4WTP0BTuFKD1i/CLL774wvVsxNOM+bZt29qSJUvs66+/3u5ztHqBHyflBWVYS2862g/0Bq+e/qBMttP+60eRZ5/9Zx19rfuslR/yeM2/tNKG5kxUqWLZS5b4cjUeXdQnCPTmoovo6JobQeLH0VXV4euCWbnpYnveKjF6E9fqMHrjV6jyaxBVKYwf6f3bO5nyqEPLu3iZOrnUM63/NeHcT+/P8bSNfqNlo3PnB1HJiXftDbWrsoTmMGq9fd38yo/7cHZ2tlsYZcOGDW7ys0ZP4i/CqbX1vesaeNTR7Ldrn2g/GD16dGySse/CvoZMFMbUyKqVjKf6tPgzrCCG/TAJStgPKj+G/chLL1mkZ8/Ykptuec08XhfdNjk6W2tt+/TqjUEJ+0Hlx7AfJn4MSmHix7AfNuzD6Q/7aSvj0fDJfffdl+dz6gEH4I+grwtmaR19La/prc4TW6VHPfrPPefboA8AQKZLW9hXnVTfvn3T9e0BJBL0+/VzV8bVBbO0jr5bXlOr7lSv7mr0o7pwmQ9LdwAAgE8m6ALwd9A3leqUK+fW0OeiWQAABIsvlt4E4OOgDwAAAouwD2Q4gj4AAOFF2AcyGEEfAIBwI+wDGYqgDwBA+BH2gQxE0AcAIDMQ9oEMQ9AHACBzEPaBDELQBwAgsxD2gQxB0AcAIPMQ9oEMQNAHACAzEfaBkCPoAwCQuQj7QIgR9AEAyGyEfSCkCPoAAICwD4QQQR8AAAhhHwgZgj4AAPAQ9oEQIegDAIB4hH0gJAj6AAAgN8I+EAIEfQAAkBfCPhBwBH0AALAjhH0gwAj6AABgZwj7QEAR9AEAQH4I+0AAEfQBAEAiCPtAwBD0AQBAogj7QIAQ9AEAQEEQ9oGAIOgDAICCIuwDAUDQBwAAySDsAz5H0AcAAMki7AM+RtAHAACFQdgHfIqgDwAACouwD/gQQR8AAKQCYR/wGYI+AABIFcI+4CMEfQAAkEqEfcAnCPoAACDVCPuADxD0AQBAUSDsA2lG0AcAAEWFsA+kUZkRIyzSs6dFolF3P7tfP4sOHmwWifB7AQAAhUbYB9IY9Hfp14+gDwAAikwpC4m6deta5cqV070ZoVS9evV0b0L4vPiiWb9+Ztt69LN697b1t99utnZturcsdH777bd0b0Ko/fLLL+nehFD76aef0r0JobZw4cJ0b0LozZw5M92bEFpbt25N6HX07APpCPoXXJAz6N9zD6U7AAAg5Qj7QBqDvl12GUEfAAAUGcI+kMagb0OG0KMPAACKDGEfKA4EfQAAkAaEfaCoEfQBAECaEPaBokTQBwAAaUTYB4oKQR8AAKQZYR8oCgR9AADgA4R9INUI+gAAwCcI+0AqEfQBAICPEPaBVCHoAwAAnyHsA6lA0AcAAD5E2AcKi6APAAB8irAPFAZBHwAA+BhhH0gWQR8AAPgcYR9IBkEfAAAEAGEfKCiCPgAACAjCPlAQBH0AABAghH0gUQR9AAAQMIR9IBEEfQAAEECEfSA/BH0AABBQhH1gZwj6AAAgwAj7wI4Q9AEAQMAR9oG8EPQBAEAIEPaB3Aj6AAAgJAj7QDyCPgAACBHCPuAh6AMAgJAh7ANC0AcAACFE2AcI+gAAIKQI+8hsBH0AABBihH1kLoI+AAAIOcI+MhNBHwAAZADCPjIPQR8AAGQIwj4yC0EfAABkEMI+MgdBHwAAZBjCPjIDQR8AAGQgwj7Cj6APAAAyVKl0b0Am2bBhg3344Yf222+/2ZFHHml77713ujcpPLKybP6jj9q7L75opdavtw5Nmli9f/3LbNMms4suMotG/3ndZZeZDRliFomke4sD6bPPPrPvv//emjZtascff3y6NyewZsyYYV999VXs/jnnnGO77LJLjtf88ssvNm3aNCtZsqQdeuihVrdu3TRsaTAtW7bMPv/889j9tm3b2q677prjNevXr7fp06fbmjVrbLfddrODDjrItTUSo/abP3+++79WrVq2xx577PB1X3/9tXvNnnvuSfMmKBqNuqywdu1aq1ChgtWpU2e7/fOPP/6wlStXWrly5axevXpWqhSRriDWr19vWVlZrl0rVqy4XftmZ2fbX3/9ZVu2bHG/A7VzUKW9Z1+N+d5779ngwYNtyZIlFlbz5s2z1q1b22OPPeYOkFdccYWNGjUq3ZsVDmPH2tu77mr7XnutTfnuO3t/3jzb6733bEr37ma9exP0U0AHxH/96192ySWX2Jw5c+yFF16wfv36peJLZ6TNmzfbunXrbPHixTZkyBD3hhJv4MCBdtVVV9nMmTPdCVaXLl3szTffTNv2Bo3enP/++2/Xxi+//LL9/vvv250MaP/96KOP3HOvvPKK9e/f3+3nSOxk9amnnrIff/zRtd+rr75qI0eOdAE1t7feessmTZpks2bNomkTpP1QuWj27NluH/7hhx9cO6rD0PPNN9+4dtXJql737rvv2saNG2njBESjUVu4cKE7Wdq0aZOtWrXKfvrppxx//2rLn3/+2VasWOE+1jFj9erVgW3ftJ4GvvHGG3bddde5M/6pU6day5Yt3dlpGF100UV20kkn2b333uvub9261YUmFNLYsRbt3NkUO28ys1u2PfxvM7vCzL72XtehAz36hfDAAw/YokWL7NNPP3U9IN4bPpKjXmTd9AaTV4g/4YQT7KabbrLIthEonVw99NBDdvrpp9PkCahfv75169bNvZGPHTt2u+cV8mvXrm0DBgxw9xWiLr74YtfTr04Z7Fy1atXciX/ZsmXd/VatWtnDDz/s/lfbexRS1XuqkUAkTn/3bdq0scqVK8c6Rd9++20XPvfdd18X8HXydPLJJ1vNmjVjnaZ67MADD6SpE7D77rtb+fLlc4ykKth7I6i//vqre6/TccI7QQjyyVRaw37VqlVdWUuZMmVyHCDCRm8gCkbPPvus62VS0NdBcZ999kn3pgWbzsJ79LCZ+kM1s25xT+njh8xM/Xm764GpU3WqbhbgYbh0eumll+zmm292YV+hXyVoKkVD0Tj88MNz3K9Ro4YbDdAbjncCgOTpPSe+5EEflyhRIhZesXO5O+VU4qD9Uu9tHvVI6/29R48e9sEHH9CkBaD9MH5fVNtq//T+9tXLrCCqoC96rkGDBrZgwQLCfgIikUiOoO+1ode+OvlXL7+Cv3rzddwNehlPWsP+cccd5/4Pc/mOzJ071+1YF154obVo0cIdEBWc1Muv0ggk6bXXzFatssX649UbUNxT3qnjYi/sr1pl9vrrZt3iTwmQCA1xarhTJ6vq0WvcuLEru1PP09NPP00jFjG98ahnv2PHjgT9FOnQoYPrJR00aJDraFK5lOr6NdqCglM5iU5I408Cxo0b5zq1dMxAcrSPamRk+fLlLtw3a9bMPa4StdxzfBRG9Vokbu3ata63XjeV/qnKRDQiqOCv3n3vpEvzJ/R8UPfnwM3m8H4x8b8sv1O41xt2nz59rGvXru6xgw8+2AX+c889151RIgmjR+t03KLZ2e5ufH+n93GsglRtrHIJwn6B6SAo6jl67rnn3Meqd9ak0Z49e1L2UIR0rLv22mutSpUqroYfqTuB1XuHyiQUkHSM1hC+Ps4dorBzU6ZMcSPX6sH3RktUT673vNwjVCgYjeYpeGr/1P9q09KlS9OMKRKNRl3bqgxKN7W32leP66Zg74V7nUxpfooqUoI4uhq4lHn33Xe7Nz7vFoTyH82il8MOOyz2mIKS3mx0xo4krVihYkbXo69QvyzuqV+3/R9bv0QnBCtX0tRJqF69uhu+jN9/GzZs6FY3Uc8TioaC52VaPco03WQIJSYppFGqRo0auXkROmG98847XdjPq74fOzZhwgS36pGCfvxqRx9//LErMVHZ3+TJk+3PP/90QUknBkjcXnvt5ToGTzzxRNezr5MoL3jGT9YV3c9dmoKdq1KlSmwlqUqVKrnyKPFOqNTOHrWtTgi8zq+gCVzYv/HGG93kFO+m1Sz8TiFJvUXfffdd7DEtX6idK/dycCiAGjVcj30LM9MUmhFxT400c4/Xie/Zr16d5k2CliM7+uij7dtvv409puFNvYGrpAepp46Avn37umOESqaCXCvqR+olje8h1eiqeqX1OBKjOnyFT50s5X4fU0BVUFLds25eSGK1o8T//tVmuXk9ygqoeo1GqES90JpL5U0mxc5t3rw5x/yS3BTsdUyIP6HSvqv2D+rypqWCPnElCBT077jjDrv66qvdesPaybRUmZbXo4SnEE47TUs6mVbGHWxmF6jGUaUPZvaKFuqJf60OnKxkkrT//Oc/rs5Z804U8LWCTPv27V3dPgpu6dKlbvUM9SbLiBEjXM9dp06dXI+oevS1NJwmQb+oi8JtozJAeu/yp5pmhVHvDV09zFr+WCOqqis/9dRT3epGWvJUwUnzqjR37NJLL2V3TrBGX7306sjSfAeP1tFXex577LE5Xu+tBc+1ORKjIK99Vtd/0EmpjhPqXPHmOaq0RG09fvx4N8qq0K9gqjmByN+WLVvcMVgnpOrMUrmkjgXeSjzKZVqtR3X63gmqJupq3w5iCU8gw35QaZhTS4tqeFMHvXfeeYc/zMKK64U728yaa01n9USb2bfb7jv646xa1ezMMwv9LTN5OFnL444ePdqtsnHXXXe5pWSR/JuN2lGrwqhnVD1zuu+FU00WVe9o7gl3ea1jju2pnRT45TR1Cmw7AfCG4DVxVCFJPdNq9yOOOMKuvPJKN5KC/GkyrvZRie+t31FvqVbvCmqPaDrohFSBXiOoCqLaV9XeOl54dKKlEmGdSOn1ek3QOkLTpXz58q69dFKlY4K3xGb8Pqr2V1bTSYACvl4f5BHWSDSN7x5aE1a9LyrH0XrHmvSn9Xh14NUtEfplqe5KS055a9Ii9TXbvqPezgsu+P8LZu2IdxY+ZoxZx47mRzpYo+gEodQvyHStABSd+J5zpJ5GdVC0vLkGSD3vmk3K0TvLwGmt2dcZk4aqNQSlXhWdVel+kK9ShjQEfV0wy1sOy1vZyPtfPfo+DvoAAABFKa3jahqGil/hAyhw0NdqJUOG/HPBLK2jr+U11VOu0QjV6Kt0J8BDbwAAAIVBER2CH/RVqqNArzX0WUcfAAAguEtvIkPtLOgDAAAgT4R9+B9BHwAAICmEffgbQR8AACBphH34F0EfAACgUAj78CeCPgAAQKER9uE/BH0AAICUIOzDXwj6AAAAKUPYh38Q9AEAAFKKsA9/IOgDAACkHGEf6UfQBwAAKBKEfaQXQR8AAKDIEPaRPgR9AACAIkXYR3oQ9AEAAIocYR/Fj6APAABQLAj7KF4EfQAAgGJD2EfxIegDAAAUK8I+igdBHwAAoNgR9lH0CPoAAABpQdhH0SLoAwAApA1hH0WHoA8AAJBWhH0UDYI+AABA2hH2kXoEfQAAAF8g7CO1CPoAAAC+QdhH6hD0AQAAfIWwj9Qg6AMAAPgOYR+FR9AHAADwJcI+CoegDwAA4FuEfSSPoA8AAOBrhH0kh6APAADge4R9FBxBHwAAIBAI+ygYgj4AAEBgEPaROII+AABAoBD2kRiCPgAAQOAQ9pE/gj4AAEAgEfaxU2VGjDC74AKzaPSfBy67zGzIELNIhJYDAADwOcI+dhr0d+nXj6APAAAQUKUsJKpXr26VK1dO92aEq3QnLuhn9e5t6wcMMFu1Kt1bFjqLFy9O9yaE2k8//ZTuTQi1mTNnpnsTQm3u3Lnp3oRQ++abb9K9CaE3a9asdG9CxqNnH/nW6Lugf889lO4AAAAEDGEf+U7GJegDAAAEE2Ef/49VdwAAAEKFsI9/EPQBAABCh7APgj4AAEBIEfYzHT36AAAAoUXYz2QEfQAAgFAj7Gcqgj4AAEDoEfYzEUEfAAAgIxD2Mw1BHwAAIGMQ9jMJQR8AACCjEPYzBUEfAAAg4xD2MwFBHwAAICMR9sOOoA8AAJCxCPthRtAHAADIaIT9sCLoAwAAZDzCfhgR9AEAAEDPfggR9AEAALANPfthQtAHAABAHMJ+WBD0AQAAkAthPwwI+gAAAMgDYT/oCPoAAADYAcJ+kBH0AQAAsBOE/aAi6AMAACAfhP0gIugDAAAgAYT9oCHoAwAAIEGE/SAh6AMAAKAACPtBQdAHAABAARH2g4CgDwAAgCQQ9v2OoA8AAIAkEfb9jKAPAACAQiDs+xVBHwAAAIVE2Pcjgj4AAABSgLDvNwR9AAAApAhh308I+gAAAEghwr5fEPQBAACQYoR9PyDoAwAAoAiUKoovijxkZdmfzz5rX778slVev96OaNjQSnXpYrZpk9lFF5lFo/+87rLLzIYMMYtEaMYk/PTTTzZ//nzbd999rU6dOrRhkmbPnm2LFy92H5csWdKOO+647V6zatUq97pddtnF9t57bytdujTtnaClS5faL7/8Ert/0EEHWdmyZXO8ZvPmzfbzzz/b+vXrrW7durb77rvTvgWg9lu2bJlt3brVtV2FChXyfN1ff/3lfhd6Tc2aNWnjAlizZo1rv8qVK1ulSpW2e37t2rXupmNEtWrVaNsC2rRpk23cuNEdW8uVK7fd89Fo1LKystw+Xr58eXesBnwb9mfMmGG///677bPPPuEMaGPH2ph//cvO//tva6k3ejOr8O239vHo0bZr/OsI+klTILr00ktt6tSptv/++9uvv/5ql112mZ133nkp+AVmnh9++ME+//xz++OPP1yg//LLL3M8P3DgQJs4caI1btzY/e3qDWfw4MHWvHnztG1z0ML+lClT3H77/fff25NPPpkj7OtE684777SKFSta9erV3e9AJ1y9evVK63YHxbfffmvjx493IVR+++03a9eunR188MHbvfbNN9+0BQsW2PHHH0/YT5ACvo61W7ZscSH0zz//dO/drVu3thIl/ikY+Oabb2zevHlWo0YNW7lypTthbdWqlUXoyMqXwruOEQr6ZcqUsQ0bNrjjwx577BFrXz23ZMkSy87Ods/pxEBtrNAP+Crs64DRuXNn++6772zPPfd0B+ibb77ZbrnlFguNsWNtQ+fOprfoO83sSp2tm9mxZqaf8knvdR060KNfCLfeeqs7OE6bNs29watXb9KkSSn5FWai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8o5GALUf63oGSI3XX3/d1q1bZz179gxdk9KzXwz69u1rTzzxhAtLl1xyiZuEp3IIrVuO1Pj888/dUlkrVqxwQ/R6Q9dFiY444giaOAVLx5522mluqF4Ty71VeNQD3aZNG9q3kD777DO7++67XS/dHnvsYfPnz7f77rvPtS1LySIIDjnkEFfep3CvUKr9uUWLFpSZpIiuBzFs2DDXxloJTZ0B6njR9U+4sFZqS3hOOeWUUF70NBJlSnexWLZsmSt/+OGHH1xto9Yob9WqVfF88wygP1CdkcfTRCadVKFwdLGcp556arvHdTI1YcIEmjcFVDuupXj1Jq6rE59wwgku/KtWF0VzvOjVq5edddZZNG8K6OqjDz/8sFv/vUqVKu5EtU+fPsxJSyGdRKkTQBlCx4gzzzzTlVUiNVauXOk6tVQ+GbZ6fSHsAwAAACFFzT4AAAAQUoR9AAAAIKQI+wAAAEBIEfYBAACAkCLsAwAAACFF2AcAAABCirAPAAAAhBRhHwBSbMGCBTZmzBgbN24cbesTS5YssTfeeKPQX+e3336z1157LSXbBADFgbAPIOMpCA4fPtzdRowYYePHj7cVK1Yk1S6vv/66HXDAAfb888/bBx98kPFtWxwWLVpkb7755k5f8/nnn7ur5hbWt99+a+eff36hvw4AFJdSxfadAMCnFAS7du1qZ5xxhpUqVcoWLlxoM2bMsIceesh69+5doK81dOhQu+SSS+zee+8tsu1FTlOnTrXLLrvMTj/99B02Tf369d3vFwAyDT37ALCNeuPVu6/wf/XVV7sAuXr16hzt8/fff9tHH31k7777rv366685ntPnzp071xYvXuw+/v777xP6vM2bN7vXr1q1yubMmePKTebNm5dj5GHs2LE2adIk++uvv7YrGdJz2dnZrtf5rbfecj3deVm6dKkrLVI43rJly3bP7+z77MiOPmfdunXuZ1q7dm2O169Zs8Y9rv9T8fP9/vvv9tlnn9mmTZtiozM6Ucutbt261qFDh6Ta7YsvvrB33nnH/V4L2g4bNmxwo0U6gYz3/vvv25QpU3b49QAgZaIAkOFee+21qA6H69atiz02ZcoU99hnn30We+y9996L1qxZM9qmTZvoKaecEq1SpUr07rvvjj1/zjnnRKtVqxbdf//93cevvPJKQp+3atUq973at28fbdq0afTMM8+M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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABW0AAAGGCAYAAAAAW6PhAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAOt1JREFUeJzt3Qm4VWW5OPDvACoCiiOogKippIKJE045CzmiecshNL1oaamPVt4y6/419T63OVPLUnMop5s3bTBNzTlxFsUkB5wnnIHDoAz7/7yLuw/nwEEBgf2tdX6/59mevdfeh/OdtV/Xefe73u9bTbVarZYAAAAAAMhCp0YPAAAAAACAORRtAQAAAAAyomgLAAAAAJARRVsAAAAAgIwo2gIAAAAAZETRFgAAAAAgI4q2AAAAAAAZUbQFAAAAAMiIoi0AAAAAQEYUbQEASqhv377piCOO+MhtLJ19DwAAi5OiLQBAZjbeeOPU1NSUfvjDHzZ6KKWz2mqrpaOOOmqBXz9w4MBiX8etU6dOaYUVVkjrr79++rd/+7d01VVXpZkzZy7R8QIAQHsUbQEAMvKPf/wjjR07tujmvPDCCxfqe19++eV0ySWXLLGxVdWAAQNSrVZLs2bNSq+//nq67rrr0uDBg9PRRx+dttlmm/TGG2+0eb39DADAkqZoCwCQkQsuuCCtvfbaxdennnoq3XHHHY0eUofSvXv3ovv21FNPLfb9448/ng455JBGDwsAgA5G0RYAIBMTJ05Mv//979OXv/zlNGzYsLThhhsuVLdte2utTp06NZ100klpjTXWKAqS8e8+++yzRQfpzjvv3Oa1PXr0SMcdd1y6//7707bbbpuWX375tO6666bzzz9/np9Vf+1dd92Vttpqq9StW7e05ZZbpgceeKB4PgqeW2+9dfFvfPKTn0x/+9vf2h3zb3/72+J18f3xb+6xxx7Fz1/YcTU3NxdLHLz99tvpoosualnyYIcddkiLavPNN0+HH354uvXWW1t+r/nt5/jd6z+zS5cuaa211iq+95VXXvnY70d0X2+33XbF733mmWcu1M9bXO8TAABLl6ItAEAmLr/88jRjxoxiTdYoxh1zzDHpmmuuSe++++4i/5vRJXrxxRenX/ziF+m1115LZ5xxRjrhhBOK4mF7nnvuufSjH/2oWGbh1VdfTZ/73OfSscceWxT95jZu3Lji37366qvT888/n/r165f22muvosh4zjnnpCuuuCK9+OKLRefqgQcemN55550233/KKacUv2uMMX7u008/nTbaaKO04447pgcffHChxhXFyVjiYNVVV00jR44s7sft7rvvTh/H0KFDi68f1fEc463/zEmTJqU//OEP6YknnkjDhw8v3tNFfT+eeeaZ9IMf/KAoREfn9ac+9amF+nmL430CAKABagAAZGHw4MG1Qw45pOXxu+++W1t++eVrP//5z+d5bZ8+fWpf/OIXP3TbqFGjapHunX322W1e9+CDDxbbd9pppzbbu3fvXlt11VVrkyZNatk2ffr0Wq9evWqHHXbYPK9dbbXVas3NzS3bnnrqqeLf7devX23ixIkt28eNG1dsP++881q2jR07ttbU1FQ79dRT2/y7s2bNqm2++ea1YcOGLdK44nUjR46sLahNNtmkNmDAgPk+f++99xZjP+mkkz5037fn7rvvLr43/o1FfT969OhRe++99xbod5n75y2O9wkAgMbQaQsAkIGHH344PfLII+krX/lKy7aVVlqp6Mxc2AuS1d12223F13333bfN9i222KKYTt+e6HKNrtW6mHofF+qKKfxz+/SnP11M8a9bf/31i9dvttlmaYUVVmjZvt5666Xllluuzb9x/fXXF12i0THbWnQY77rrrvN0ti7MuBanGGN9XB8m1r6N32XNNdcsxtZ6aYboll3U92OnnXZKPXv2XKSftzjeJwAAGkPRFgAgA3HhsXqBrb5Wadx+85vfpMcee2yedV4XRKzvGnr16jXPc+1tC1EEnNuKK66Y3nvvvY98bYw3ioPt/RtRcG39b7z++ustBcsoIHbu3Dl16tSpuMUyCNOmTUtTpkxZpHEtTi+//HLxdX5F1RDLNWy//fbFWG644YZibeIo9o4ePbp4fvr06Yv8fvTp02eRf97ieJ8AAGgMRVsAgAaL4mSsKxodtfV1SlvfotuyXtRdGLG+a3jjjTfmea69bQvSUbogr12Qf2O11VYrvj755JPFGqwzZ85Ms2bNKm713zsumrUo41qcbrrppuLr3BcJa+26664rCqfnnXde0b1aH3esw/tx349llllmkX/e4nifAABoDEVbAIAGiwtERRFu2LBh7T6/5557pquuuio1Nzcv1L+7yy67tCxF0FoswxDdmo209957F0XD+N0Xp+ggff/99xfbkhW//e1v0+677150BH+UWFqgtcsuu2yJvh8f9fMAACgvRVsAgAaLDtuBAwemvn37zrdoGwXbKNwujG222SYNHz48/ed//mfRnTlp0qT0wAMPpP/3//5f2nTTTVMjxe/7rW99K51++unp+9//fnrxxRfT1KlT0xNPPJF+/OMft1nbd2H/3VhKYlGL0jGGf/7zn+mss84qumtjP1155ZUf+j1Dhw5NXbt2TSeffHIaP358eu2119K3v/3tYtmHJfF+LOjPAwCgvBRtAQAaaOzYsemee+5Jn/nMZ+b7mijoxdqmi7JEQhQcDz/88PTlL385rbHGGunUU09NP/vZz4q1Y+fu1Fza/uu//itdfvnlxbqsgwYNKpZMOOigg4qlAqIIuShiPdzVV1+9uNhW64tzfZhYoqG+hnB873777Vd0v/76178u3pv6Ug7zEz8rirDjxo1L6667bhoyZEhafvnl02mnnbZE3o+F+XkAAJRTU61+SVwAADqMKE7utdde6dJLL230UPB+AAAwF522AAAdzJ133pneeuut4gJnNJ73AwCAuem0BQCosOikjYuc7bvvvmmVVVZJo0aNSsccc0yxJmpcaCum1eP9AAAgLzptAQAqLC589eyzzxYXr4olEY488si02267pdtvv13B1vsBAECmdNoCAAAAAGREpy0AAAAAQEYUbQEAAAAAMqJoCwAAAACQEUVbAAAAAICMKNoCAAAAAGRE0RYAAAAAICOKtgAAAAAAGVG0BQAAAADIiKItAAAAAEBGFG0BAAAAADKiaAsAAAAAkBFFWwAAAACAjCjaAgAAAABkRNEWAAAAACAjirYAAAAAABlRtAUAAAAAyIiiLQAAAABARhRtAQAAAAAyomgLAAAAAJARRVsAAAAAgIwo2gIAAAAAZETRFgAAAAAgI4q2AAAAAAAZUbQFAAAAAMiIoi0AAAAAQEYUbQEAAAAAMqJoCwAAAACQEUVbAAAAAICMKNoCAAAAAGRE0RYAAAAAICOKtgAAAAAAGVG0BQAAAADIiKItAAAAAEBGFG0BAAAAADKiaAsAAAAAkBFFWwAAAACAjCjaAgAAAABkRNEWAAAAACAjirYAAAAAABlRtAUAAAAAyIiiLQAAAABARhRtAQAAAAAyomgLAAAAAJARRVsAAAAAgIwo2gIAAAAAZETRFgAAAAAgI4q2AAAAAAAZUbQFAAAAAMiIoi0AAAAAQEYUbQEAAAAAMqJoCwAAAACQEUVbAAAAAICMKNoCAAAAAGRE0RYAAAAAICOKtgAAAAAAGVG0BQAAAADIiKItAAAAAEBGFG0BAAAAADKiaAsAAAAAkBFFWwAAAACAjCjaAgAAAABkRNEWAAAAACAjirYAAAAAABlRtAUAAAAAyIiiLQAAAABARhRtAQAAAAAyomgLAAAAAJARRVsAAAAAgIwo2gIAAAAAZETRFgAAAAAgI4q2AAAAAAAZUbQFAAAAAMiIoi0AAAAAQEYUbQEAAAAAMqJoCwAAAACQEUVbAAAAAICMKNoCAAAAAGRE0RYAAAAAICOKtgAAAAAAGVG0BQAAAADISJdUQrVaLU2ZMqXRw2ARdevWLTU1Ndl/cxHX5Se22ye2y01cz5/YLjex3T5xXX5iu31iu9zE9fyJ7XIT21SuaBsF2x49ejR6GCyi5ubm1L17d/tvLuK6/MR2+8R2uYnr+RPb5Sa22yeuy09st09sl5u4nj+xXW5imw9jeQQAAAAAgIyUstO2tfHjx+vaLIHJkyen3r17N3oYpSGuy0NsLxyxXQ7ieuGJ7XIQ2wtHXJeH2F44YrscxPXCE9vlILbpMEXbmGZvqj1VI66pKrFNVYltqkhcU1Vim6oS21AtlkcAAAAAAMiIoi0AAAAAQEYUbQEAAAAAMqJoCwAAAACQEUVbAAAAAICMKNoCAAAAAGRE0RYAAAAAICNdGj0A2vfUU0+lxx9/vLi/wQYbpEGDBtlVVEJzc3N66KGHiq/9+/dPm2yySWpqamr0sOBje+utt9Lo0aPT9OnT0yc+8Ym04YYb2qtURsT1DTfckNZZZ5206aabNno48LH9/e9/TxMmTGh5HLl25NxQdrNmzUqPPfZYevHFF9OMGTPENpVwxx13pLfffrvNti222KL4PAlVptM2UyuttFJaf/310/vvv59ee+21Rg8HFoso1F566aVFMrn66qsXH5huv/12e5fSGz9+fLruuutSt27dUs+ePdOf//zn4gMTVEUcr1999dXiBlXw9NNPF7lI5Ntxi9wbqiDykfvuuy+tscYaYpvK6Nu3b8vxOgq10eC24oorNnpYsMQp2jZIFK2iY+XNN98sHkdxNj7kT548uXjcq1evopMlkkkoW7fhX//61yLGQ5zlv+2224r7yy67bBo5cmTaZZdd0tZbb52GDRtWdJVDGTz66KPpkUceaXk8atSolviNpPHf//3f03bbbVfcNt5446KQC2XPR8ILL7xQPNY9TlXykbrorI18W85NVfKRV155pTghccQRRxS5ttimKvlIzGKrH687d+5czPxZeeWVGzxqWPIUbRukU6dOab311kvXXHNNMeXw+uuvTz169Ejdu3dv1JBgsVhttdXSBx98UHRlTZkyJV177bUtH/SjaNu1a9eW177++utFFwCUQXy4v+uuu9JLL72UnnzyyeJDUxzHw/LLL18kl3/4wx/S7373u2L61rbbbtvoIcPHzkfieH7LLbekPffc096kMvlI3T333FPE/JgxY1KtVmvYWGFx5SNxciI6ESOmo+AVS5LVT1xAVeojcdJi8ODBDRknLG3WtG2gAQMGpOeffz5ddNFFRSFr//33b+RwYLHZa6+90oUXXpjGjRuXhgwZkvr06TPPa5555pliWsuRRx5pz1MKsfTBAQccUHzwnzlzZjrssMNSly5z/ozG/figNHHixPTAAw8US9tYH5Gy5yM333xzcQIi4h+qlI/stttuaerUqUVB98477yyKXXvvvXdDxwsfNx+JrsTnnnuuOJavueaa6cEHHyyaJMQ2VamPxCyK6MbdaKONGjJGWNp02jZYfKCPKbQDBw4szi5BFURH7dprr138UY1p4nOLKVw33XRTkWTqLqdMevfuXXRjrbLKKkUXV2vLLLNMMWVrhx12SDvvvHMxXRHKnI/ECYhYm/lf//pX0UUeHV1R/IruRCh7PlJfGmGbbbZJBx98cNGtqNuWsucjkVfH2vpRpN1yyy3T8OHD0xNPPNHQscLirI88/PDDxcX1WjdOQJWpEjZQnNmPaSsHHnhgMcVl7qshQlnFB/uYsrX77rsXH/RbT8uKxDE6t77whS8USSWUSayPGB/8Yy2te++9t2X7yy+/XEzDrXv33XeLJROgzPlIdLnEB//6hT9i7bg4bq+11lqNHjJ87HyktThmL7fccqmpqcmepdT5SKzzGcf06MCtn3yL2IYq1EciruNksqUR6EicnmiQODMaU1q22mqrlrNIsX5LXKQpzhrF1ZnjD3BMZ4nXNjc3F50APiiRuwkTJhSJ5IgRI4oL6cUFEW6//fa06667pnfeeaeI87jiZ6wxF6KwZa1EymD06NFF8rjffvsVU2ovuOCC1K9fv2K6bay9FY9XXXXVYmrie++9V3SSQ5nzkehSjE7EuuhWjJMTURSAMucjkyZNKk4ghzieR2E3llKAsucjsSRC3P/lL39ZxH1MNbc0AlWoj9RPxMXFf10ThY6kqVbCeUDxgTgWpQ5RzCzj9Oo4ixTrDcUZ0vpZ/Zgy3qtXr7TSSisVZ0Xjj2xr8SEpDlJlVIX3bEmryj6KEw7RydK3b9/icVycKWJ7k002Ke7HFW3nnlJe5jWJqvK+LUlV2Udjx44t4nqFFVYoHr/xxhvFsbxewIoPTrEmYhS64nUR22VVlfdsSesI+UhrcSI5ju9lPoFchfdsSesI+UicaIsP//WO8ojp+u9cVlV535akjpKPhMhH4veN2C7zzLaqvGdLWkfJR+LkWxRz4+RE2VXhPWPpULRlqXBQso+qSmzbR1Ukru2nqhLb9lFViW37qIrEtf1UVWKbBWVNWwAAAACAjCjaAgAAAABkRNEWAAAAACAjirYAAAAAABlRtAUAAAAAyIiiLQAAAABARhRtAQAAAAAyomgLAAAAAJARRVsAAAAAgIwo2gIAAAAAZETRFgAAAAAgI4q2AAAAAAAZ6dLoAXRkM2bMSHfffXd67bXX0pAhQ9J66603z2uefvrpdP/996eePXumXXfdNXXr1q0hY4WF8c4776Q777wzNTU1pV122SWtuOKKbZ5/880303333ZemT5+eBg8enNZZZx07mFL417/+lR555JHUt2/ftMMOOxQx3p4HHnggPfroo8Vxu71jO+REPkJHzUcmTZqURo0aVeQlm266aRo0aFDDxgqLMx954YUX0kMPPZRmzZqVdt5557TaaqvZwWQrYjXiuT1HHHFE6tJlTtnqjTfeSDfccEPq06dP2n333ZfiKKExdNo28A/tJptsko455pj017/+NR1wwAHpnHPOafOaH/zgB2mzzTZLV199dTrllFPSRhttlMaNG9eoIcMCufjii1P//v3Tueeem6666qq03XbbFcWruq9//etpiy22SOeff3665JJL0sYbb5y+853v2Ltk7YMPPkiHHXZY2mabbdK1116bzjrrrDR06NA0c+bMeV770ksvFcf0o48+ujjpBjmTj9BR85G77rorbbDBBum73/1u+vOf/1ycZDvuuOMaOmZYHPnI9773vSK/vuyyy9JvfvOb9IlPfKKIcchV5M733ntvm1vEcRyfO3WaXbKaMGFCOvTQQ9Pmm2+eTj311OLYDh1CrYSam5trMfS4xf2ymTp1au0Tn/hE7aCDDqrNmDGj2BZfb7vttpbXjBkzptapU6fatddeWzyePn16baeddqoNHTq0VkZlf8+Whirso7vvvruI2yuvvLJl2+uvv17Ec93ll19emzZtWsvjm2++ufid43vLqArv25JWhX30ta99rbbmmmvWXnzxxZZtt99+e3Fsbi2O5TvssEPtnHPOKX7f1v8vlEkV3rOloez7ST5SvvdsaSh7XC9oPtK/f//ayJEjWx6//PLLtRVXXLEl9y6bKrxvS1pHyEceeOCB4ve74YYbWp6/+OKLayuvvHJtwoQJtbKpwnu2NFRtP0V+stJKK9VOOeWUlm1vvvlm8Tny/fffrx144IG14cOH18qsau8ZS45O2wa45ppr0nPPPZd+/OMfp86dOxfb4mtMXamL7tq11lor7b///sXjmBLwla98Jd18883prbfeasSw4SP98Ic/LM78H3zwwS3bevfunQYOHNjyOM6QLrfcci2PY1rLsssum/75z3/aw2Qpps/+8pe/TCeffHLq169fy/addtqpzXStcNpppxVTEI866qgGjBQWjnyEjpqPvP3228X08f3226/l+ZhqGx1cv/3tbxsyZlgc+UhMM4+8etiwYS3P77vvvundd99N119/vZ1MKfzv//5v0Vk7cuTIlm2RX8fnyIhv6EgUbRsg1rGNpQ5indqY1vKHP/whvfjii21e8/jjjxfTWlqL5RRqtVp64oknlvKIYcFje4899ihOSlx++eXFSYbm5uYP/Z5bbrmlmOoVS4FAjuID0NSpU4sTDDGd9oorrmh32YPbbrutWPLjwgsvbMg4YWHJR+io+cjKK6+cevTokR577LGWbe+//35xLYnIwaGs+UgUcyOvfvLJJ1u2jRkzpvgqtimLyKVjyZpY2gM6Ohcia4A4ux+Lwm+99dZpwIABxR/WL3zhC+nMM88s1vsMEydOTKuvvnqb71tllVVanoPcxAmFuODHPffcU3SpRHyPHTu2WCz+T3/6U9pyyy3n+Z7x48cXZ1A///nPF6+HXI/Z4dvf/nYRz+uuu25RoI2Oreha6dq1azEDItaYi7XjVl111TRt2rRGDxs+knyEjpqPxBqJZ5xxRvrWt75VHL/juP773/++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haJtgxx++OHp6quvTtdff33RjfWLX/yi+ANc995776XPfOYzacsttyzu33333cWH/yj0Qq7eeOONtMMOO6TlllsuvfTSS+ntt99OO++8c7rrrrtaXnPFFVekoUOHpqeeeipNnjw5nX766ekrX/lK+stf/tLQscOHufbaa9OBBx5YxGrE7WOPPVbc6olka9/97ndT37597VBKQT5CR81HDjvssPTQQw8Vt+jaOvnkk9O+++6bnnzyyYaOHT5OPvLaa6+lYcOGFUXdN998s/h/IYq7++23X9F1DjmK3Lm5ubnNbYMNNihOOMRxPLzwwgvpwgsvLBrZhg8f3ughw9JTK6Hm5ub4i1Pc4n7Z3H333cXY77jjjvm+5txzz61169atNmXKlJZtP/rRj2o9evSoTZs2rVY2ZX/PloYq7KMTTzyx1rdv34WO0X79+tVOO+20WhlV4X1b0sq+j2bOnFnr379/7dhjj/3I195888219dZbrzZ+/Pji973yyitrZVT292xpKft+ko+U7z1bGsoe1wuSj0yaNKnW1NQ0zzF6yJAhta9+9au1MqrC+7akdYR85NJLL6117ty5NnXq1JZtL730UvE733bbbbWyKft7trRUbT/dc889xe8SeXV7DjzwwNrw4cNrZVa194wlR6dtA0RHbbT8x/TZaPOvrzfU2qhRo4ou25jSUrfLLrsUZ50ef/zxpTxiWPDYrp8RjSni8xMxH7E8fvz4dN555xXTFQ844AC7mSxFB0uc3T/ooIOKx/OL7ehmOfLII9Nll12WVlxxxaU8Slh48hE6aj4S69vGeohzLzsWj++5556lOFJYvPlIxHZ01MZ1I+rqcS62KYuLLroorbvuumm33XZr9FCg4RRtGyDW9Ix2/xNPPLFYayjWZNlpp53SI4880ubD/+qrr97m++qP4znINbZjClackIgTDrEO0Ze//OU0ceLENq+LqYdx4mKttdZK3/jGN9I555yTNt1004aNGz4qrutx26dPnyLG11tvvfTrX/+65TXxAemLX/xiUbTdfvvt7VBKQT5CR81HYltMrz3zzDPTvffeW5xE/tGPfpQeffTR4j6UNR/ZY489irVAjznmmKLA+8wzz6TjjjuuKOaKbcogGntiGcmjjjqqOLkGHZ2ibQPEh/v6mlpvvfVWsTB8LCK/1157tblAwtwduPXHDl7k7Gc/+1n61re+VaytFWsxx0U9vvrVr7Z5zUYbbVT8QY415OJM6tFHH53+53/+p2Fjhg9TXwMu1h6PY3fE7VlnnVV8IPrjH/9YPPfzn/+8WEfua1/7WhHbsc5ciP8P4vWQI/kIHTkfufjii4vrR8TatoMGDSq6GGOd0ChuQVnzkbjQ79///vf07rvvpm233ba4jkTE+frrry+2KYUo2MY1f6IRAlC0bYjoLoyEMK6SGGf6Yxpt3I/i7X333dfymrk7auuP11xzzYaMGz5KxGacfIhbp06diuLsCSecUFzpsz3LLrtsOvTQQ9M+++yTfvOb39jBZCmOxyEuUhMdLXH8PuSQQ4qO2vqHpDFjxhTdLHEBsugi79+/f7E9OrvigiCQI/kIHTkf6dmzZzr77LPT008/XeTYsbRNdCZGcQvKmo+ET33qU8UFfl999dX07LPPppEjR6Zx48aJbUohGnr23ntvNQ/4PzptGyCmasWZ0tbraNWv+BlFrBBXvH3wwQfbTCu/+eabi+kuG2+8cQNGDQsW2x988EGbbRHbyyyzzId+X3Qm1mMfchNXYI6Ta+3Fdj1u42q2ra94G7MowiWXXJLuvPPOhowbPop8hKpalHwkpo5Hrn3ggQcuhRHCkslH5te5GPbbbz+7nayNHTu2uLZPzMIEZlO0bYB99923mIbVeq2h448/Pg0YMCANGTKkeE2cNe3du3exlkucIb3pppvSD3/4w/T1r3/9Iwtg0Cj/8R//kW677baiaza6VuJ+TE+MtT7rouvlxhtvLKaSR2yfccYZxZTFiHXIUVzIJrpaYgpiJJLxwf6nP/1pevjhh9OIESMaPTxYZPIROnI+cv7556cLLrigyEceeuih4sJl0aH4pS99qaFjh4+bj0QM33777cUJ5Fh+7KSTTiry7ZgNBLl32UacxpIe7Ynlx6I5Ii60F7fWS5JBZdVKqLm5ORb0KW5xv4xee+212qGHHlrr3bt3rX///rURI0bUXnjhhTavefrpp2v77LNPbdVVV62tt956tTPPPLM2a9asWhlV4T1b0qqyj2699dbadtttV8TtwIEDa2eddVbtgw8+aHn+oYceqn32s5+t9enTp7bOOuvU9t577+J7yqoq79uSVIV9FMfe//7v/65tsMEGtV69etV23HHH2o033jjf10+bNq3WvXv32jXXXFMroyq8Z0tDFfaTfIQqxvWC5CMTJkyoffWrXy3ykcizv/GNb9QmTpxYK6uqvG9LUkfJRx599NHaHnvsUVtttdVqW265Ze3SSy+tlVUV3rOloQr7acaMGcWx+Hvf+958XxPH68ivW99iWxlV4T1j6WiK/6SSibMpPXr0KO7H2ZXu3bs3ekh8BO/ZR7OPysn7Zh9Vkbi2n6pKbNtHVSW27aMqEtf2U1WJbRaU5REAAAAAADKiaAsAAAAAkBFFWwAAAACAjCjaAgAAAABkRNEWAAAAACAjirYAAAAAABnpkkpu8uTJjR4CC8D7tHDsr/LwXtlfVSSu7bOqEtv2V1WJbfurisS1fVZVYpsOU7Tt3bt3o4cAi524pqrENlUltqkicU1ViW2qSmxDtVgeAQAAAAAgI021Wq2WSiaGPGXKlEYPg0XUrVu31NTUZP/NRVyXn9hun9guN3E9f2K73MR2+8R1+Ynt9ontchPX8ye2y01sU7miLQAAAABAVVkeAQAAAAAgI4q2AAAAAAAZUbQFAAAAAMiIoi0AAAAAQEYUbQEAAAAAMqJoCwAAAACQEUVbAAAAAICMKNoCAAAAAGRE0RYAAAAAICOKtgAAAAAAGVG0BQAAAADIiKItAAAAAEBGFG0BAAAAADKiaAsAAAAAkBFFWwAAAACAjCjaAgAAAABkRNEWAAAAACAjirYAAAAAABlRtAUAAAAAyIiiLQAAAABARhRtAQAAAAAyomgLAAAAAJARRVsAAAAAgIwo2gIAAAAAZETRFgAAAAAgI4q2AAAAAAAZUbQFAAAAAMiIoi0AAAAAQEYUbQEAAAAAMqJoCwAAAACQEUVbAAAAAICMKNoCAAAAAGRE0RYAAAAAICOKtgAAAAAAGVG0BQAAAADIiKItAAAAAEBGFG0BAAAAADKiaAsAAAAAkBFFWwAAAACAjCjaAgAAAABkRNEWAAAAACAjirYAAAAAABlRtAUAAAAAyIiiLQAAAABARhRtAQAAAAAyomgLAAAAAJARRVsAAAAAgIwo2gIAAAAAZETRFgAAAAAgI4q2AAAAAAAZUbQFAAAAAEj5+P/ICZGcSi1AfwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Step 1: Align using edit distance\n", + "GAP_PENALTY = 6\n", + "operations, D = event_alignment_ED(response_events, ref_events, gap_penalty=GAP_PENALTY)\n", + "N = len(response_events)\n", + "M = len(ref_events)\n", + "\n", + "# Cost matrix\n", + "fig, ax = plt.subplots(figsize=(9, 8))\n", + "ax.imshow(D, origin=\"lower\", cmap=\"Greys\")\n", + "for i in range(N + 1):\n", + " for j in range(M + 1):\n", + " ax.text(j, i, str(D[i, j]), ha=\"center\", va=\"center\", fontsize=9)\n", + "n = N\n", + "m = M\n", + "path_rows = [n]\n", + "path_cols = [m]\n", + "for op in reversed(operations):\n", + " if op[\"type\"] in (\"match\", \"replacement\"):\n", + " n = n - 1\n", + " m = m - 1\n", + " elif op[\"type\"] == \"extra\":\n", + " n = n - 1\n", + " else:\n", + " m = m - 1\n", + " path_rows.append(n)\n", + " path_cols.append(m)\n", + "path_rows.reverse()\n", + "path_cols.reverse()\n", + "ax.plot(path_cols, path_rows, \"ro-\", markersize=8, linewidth=2)\n", + "ax.set_xlabel(\"Reference event index\")\n", + "ax.set_ylabel(\"Response event index\")\n", + "ax.set_title(\"Accumulated Cost Matrix D\")\n", + "plt.tight_layout()\n", + "plt.show()\n", + "\n", + "# Alignment diagram\n", + "box_w = 1.2\n", + "box_h = 0.6\n", + "ref_y = 2.5\n", + "res_y = 0.5\n", + "x_gap = 2.0\n", + "\n", + "ref_x = {}\n", + "res_x = {}\n", + "x = 0.0\n", + "for op in operations:\n", + " if op[\"type\"] == \"missing\":\n", + " ref_x[op[\"reference_idx\"]] = x\n", + " elif op[\"type\"] == \"extra\":\n", + " res_x[op[\"response_idx\"]] = x\n", + " else:\n", + " ref_x[op[\"reference_idx\"]] = x\n", + " res_x[op[\"response_idx\"]] = x\n", + " x = x + x_gap\n", + "\n", + "fig, ax = plt.subplots(figsize=(14, 4))\n", + "ax.axis(\"off\")\n", + "for ref_idx, xpos in ref_x.items():\n", + " pitch = ref_events[ref_idx][\"notes\"][0][\"pitch\"]\n", + " ax.add_patch(plt.Rectangle(\n", + " (xpos - box_w / 2, ref_y), box_w, box_h,\n", + " fill=False, edgecolor=\"black\", linewidth=1.5\n", + " ))\n", + " ax.text(xpos, ref_y + box_h * 0.7, \"x\" + str(ref_idx + 1),\n", + " ha=\"center\", va=\"center\", fontsize=8, color=\"gray\")\n", + " ax.text(xpos, ref_y + box_h * 0.25, str(pitch),\n", + " ha=\"center\", va=\"center\", fontsize=10)\n", + "for res_idx, xpos in res_x.items():\n", + " pitch = response_events[res_idx][\"notes\"][0][\"pitch\"]\n", + " ax.add_patch(plt.Rectangle(\n", + " (xpos - box_w / 2, res_y), box_w, box_h,\n", + " fill=False, edgecolor=\"black\", linewidth=1.5\n", + " ))\n", + " ax.text(xpos, res_y + box_h * 0.7, \"y\" + str(res_idx + 1),\n", + " ha=\"center\", va=\"center\", fontsize=8, color=\"gray\")\n", + " ax.text(xpos, res_y + box_h * 0.25, str(pitch),\n", + " ha=\"center\", va=\"center\", fontsize=10)\n", + "for op in operations:\n", + " op_type = op[\"type\"]\n", + " if op_type == \"missing\":\n", + " xpos = ref_x[op[\"reference_idx\"]]\n", + " ax.annotate(\"\", xy=(xpos, res_y + box_h),\n", + " xytext=(xpos, ref_y),\n", + " arrowprops=dict(arrowstyle=\"<->\", color=\"gray\",\n", + " linestyle=\"dashed\", lw=1.5))\n", + " ax.text(xpos, (ref_y + res_y + box_h) / 2, \"missing\",\n", + " ha=\"center\", va=\"center\", fontsize=8, color=\"red\")\n", + " elif op_type == \"extra\":\n", + " xpos = res_x[op[\"response_idx\"]]\n", + " ax.text(xpos, (ref_y + res_y + box_h) / 2, \"extra\",\n", + " ha=\"center\", va=\"center\", fontsize=8, color=\"purple\")\n", + " elif op_type == \"match\":\n", + " xpos = ref_x[op[\"reference_idx\"]]\n", + " ax.annotate(\"\", xy=(xpos, res_y + box_h),\n", + " xytext=(xpos, ref_y),\n", + " arrowprops=dict(arrowstyle=\"<->\", color=\"green\", lw=2))\n", + " else:\n", + " xpos = ref_x[op[\"reference_idx\"]]\n", + " ax.annotate(\"\", xy=(xpos, res_y + box_h),\n", + " xytext=(xpos, ref_y),\n", + " arrowprops=dict(arrowstyle=\"<->\", color=\"red\", lw=2))\n", + "ax.set_xlim(-1, x)\n", + "ax.set_ylim(0, 4)\n", + "ax.set_title(\"Alignment Diagram\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "694cc504", + "metadata": {}, + "source": [ + "### Step 2" + ] + }, + { + "cell_type": "markdown", + "id": "0668730d", + "metadata": {}, + "source": [ + "It is common for students to play at a slower tempo by setting the metronome to a slower pace during practice. Then after becoming more and more familiar with it, they may speed up unconsciously especially for the easier parts. Therefore, to avoid prompting the tempo problem for every single note in these cases, the global drift problem must be separated from local deviations. \n", + "- `estimate_global_timing` fits a linear regression over all matched note pairs: response_start ≈ scale × ref_start + offset, where scale represents the student's overall tempo relative to the reference (greater than 1.0 indicates playing slower; less than 1.0 indicates playing faster), and offset captures any constant time shift.\n", + "- `estimate_global_duration_scale` fits a least-squares regression, this regression line passes through the origin because note duration has no meaningful constant offset term: response_duration ≈ duration_scale × ref_duration, where duration_scale represents the general holding time for notes relative to the reference (greater than 1.0 indicates longer holding; less than 1.0 indicates shorter holding time)\n", + "- To ensure the fitting method is statistically and musically meaningful, if the amount of matched pairs available is smaller than three, the functions will assume there is no global tempo trend. \n", + "- The slow/fast decision mainly depends on the scale for timing." + ] + }, + { + "cell_type": "markdown", + "id": "446b4a4c", + "metadata": {}, + "source": [ + "#### Result" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "3f97b847", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "timing_scale = 1.2135 \n", + "timing_offset = -0.0507 seconds\n", + "duration_scale = 1.2000 \n" + ] + } + ], + "source": [ + "# Step 2a: Estimate global timing (tempo drift)\n", + "timing_scale, timing_offset = estimate_global_timing(\n", + " operations, response_events, ref_events\n", + ")\n", + "print(f\"timing_scale = {timing_scale:.4f} \")\n", + "print(f\"timing_offset = {timing_offset:.4f} seconds\")\n", + "\n", + "# Step 2b: Estimate global duration scale\n", + "duration_scale = estimate_global_duration_scale(\n", + " operations, response_events, ref_events\n", + ")\n", + "print(f\"duration_scale = {duration_scale:.4f} \")" + ] + }, + { + "cell_type": "markdown", + "id": "06deb2d0", + "metadata": {}, + "source": [ + "### Step 3 - 6" + ] + }, + { + "cell_type": "markdown", + "id": "052b84bb", + "metadata": {}, + "source": [ + "Step 3: Compute metrics for each aligned event pair\n", + "\n", + "While note comparison is relatively trivial, chord comparison can be considered as a set matching problem so that the correctness of a chord can be determined by the overlap between response set and reference set. Additionally, it may require handling partial matches problem (e.g. two out of three chord notes played correctly), consider defining it as imperfect chords. \n", + "\n", + "- Pitch: exact match for notes, the absolute semitone difference is recorded for feedback; for chords, chord accuracy A = (C − I + |y|) / (2|y|) is computed (chord accuracy metric reference: https://arxiv.org/pdf/2201.05244) and the exact wrong/missing/unexpected pitch is also recorded for feedback.\n", + "- Timing and Duration: first removing the global trend, then evaluate the relative difference according to certain proportional threshold value, as a fixed tolerance (e.g. +/-0.5 s) is unfair: it is too tolerant for long notes and overly strict for short notes.\n", + " - Timing: The expected start time is predicted from the global trend line, then the relative difference in start time is calculated by ∣response_start − predicted_start∣ / inter-onset interval (IOI) of the reference note, a note is considered correct if this relative difference is within a threshold. In the feedback, the amount of difference will be reported instead of directly indicate the correctness, so that the students will not be discourage to include their expressiveness in certain parts of the practice. The first note (ref_idx == 0) is always marked as timing-correct, since normalisation in Step 0 guarantees both sequences start at t = 0.\n", + " - Duration: The expected duration is predicted from the global duration scale, then the relative difference in duration is calculated by ∣response_duration − predicted_duration∣ / ref_duration, a note is considered correct if this relative difference is within a threshold." + ] + }, + { + "cell_type": "markdown", + "id": "f6f8ed5d", + "metadata": {}, + "source": [ + "Step 4:\n", + "- Compute the summary statistics to provide overview feedback, including total notes/chords missing, extra, wrong pitch, wrong timing, and wrong duration, as well as boolean flags indicating whether all paired notes are correct on each dimension. The global trend parameters (timing_scale, timing_offset, duration_scale) are also included. \n", + "\n", + "Step 5: generate human-readable feedback message from step 3 and step 4\n", + "- Overview provides a summary of the student's overall performance:\n", + " - Tempo judgement based on timing_scale: explicitly states whether the overall tempo is acceptable, too slow, or too fast, along with the duration_scale as supplementary context\n", + " - Total count of pitch errors, missing notes, and extra notes\n", + "- Detail provides actionable, event-level feedback for each issue identified:\n", + " - Missing/extra notes: identifies the specific note and pitch\n", + " - Pitch errors: states the expected and played pitch, and the semitone difference\n", + " - Local timing anomalies: reports the absolute and relative deviation after the global trend has been removed\n", + " - Local duration anomalies: reports the deviation direction and magnitude after the global duration scale has been removed\n", + "\n", + "Step 6: provide an additional overall pass/fail judgement based on step 3 and step 4" + ] + }, + { + "cell_type": "markdown", + "id": "720a022a", + "metadata": {}, + "source": [ + "## Example feedback message:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "7bcb2de3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overview: \n", + "Overall, your tempo is slower than the reference (timing is about 21% behind the reference in general while notes are held about 20% longer than the reference). No worries! You will get better when you practice more to get more familiar with it!\n", + "There is 1 note played with the wrong pitch.\n", + "There are no missing notes. Great!\n", + "There are no extra notes. Good job!\n", + "Chords: 1/2 correct, 1/2 imperfect (some notes missing or extra), 0/2 completely wrong.\n", + "\n", + "Note Detail:\n", + "Note 2: wrong pitch — expected 62, played 63 (1 semitone(s) off).\n", + "Note 3: timing is off by 0.16s (33% of the expected note interval), after accounting for the overall tempo trend.\n", + "\n", + "Chord Detail:\n", + "Chord 6 (expected A minor, you played F major): 67% accurate. Missing note(s): E. Extra note(s) played: F.\n" + ] + } + ], + "source": [ + "# Calling compare_performance_ED() runs all steps in one call.\n", + "result = compare_performance_ED(responseMIDI, referenceMIDI)\n", + "print(result.feedback_message)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "610ec837", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "- is_correct:\n", + "False\n", + "- event details:\n", + "[{'event_type': 'note', 'reference_index': 1, 'response_index': 1, 'operation_type': 'match', 'pitch_correct': True, 'pitch_diff': 0, 'timing_correct': True, 'timing_abs_diff': 0.05071899340922707, 'timing_relative_diff': None, 'duration_correct': True, 'duration_abs_diff': 5.551115123125783e-17, 'duration_relative_diff': 1.3877787807814457e-16}, {'event_type': 'note', 'reference_index': 2, 'response_index': 2, 'operation_type': 'replacement', 'pitch_correct': False, 'pitch_diff': 1, 'timing_correct': True, 'timing_abs_diff': 0.04397843019772307, 'timing_relative_diff': 0.08795686039544615, 'duration_correct': True, 'duration_abs_diff': 5.551115123125783e-17, 'duration_relative_diff': 1.3877787807814457e-16}, {'event_type': 'note', 'reference_index': 3, 'response_index': 3, 'operation_type': 'match', 'pitch_correct': True, 'pitch_diff': 0, 'timing_correct': False, 'timing_abs_diff': 0.16276213301378073, 'timing_relative_diff': 0.32552426602756146, 'duration_correct': True, 'duration_abs_diff': 5.551115123125783e-17, 'duration_relative_diff': 1.3877787807814457e-16}, {'event_type': 'chord', 'reference_index': 4, 'response_index': 4, 'operation_type': 'match', 'chord_name_ref': 'C major', 'chord_name_res': 'C major', 'chord_accuracy': 1.0, 'correct_pitches': [0, 4, 7], 'missing_pitches': [], 'extra_pitches': [], 'timing_correct': True, 'timing_abs_diff': 0.03049730377471538, 'timing_relative_diff': 0.06099460754943076, 'duration_correct': True, 'duration_abs_diff': 5.551115123125783e-17, 'duration_relative_diff': 1.3877787807814457e-16}, {'event_type': 'note', 'reference_index': 5, 'response_index': 5, 'operation_type': 'match', 'pitch_correct': True, 'pitch_diff': 0, 'timing_correct': True, 'timing_abs_diff': 0.02106051527860986, 'timing_relative_diff': 0.030086450398014077, 'duration_correct': True, 'duration_abs_diff': 5.551115123125783e-17, 'duration_relative_diff': 1.3877787807814457e-16}, {'event_type': 'chord', 'reference_index': 6, 'response_index': 6, 'operation_type': 'replacement', 'chord_name_ref': 'A minor', 'chord_name_res': 'F major', 'chord_accuracy': 0.6666666666666666, 'correct_pitches': [0, 9], 'missing_pitches': [4], 'extra_pitches': [5], 'timing_correct': True, 'timing_abs_diff': 0.012971839424805065, 'timing_relative_diff': 0.02161973237467512, 'duration_correct': True, 'duration_abs_diff': 5.551115123125783e-17, 'duration_relative_diff': 1.3877787807814457e-16}, {'event_type': 'note', 'reference_index': 7, 'response_index': 7, 'operation_type': 'match', 'pitch_correct': True, 'pitch_diff': 0, 'timing_correct': True, 'timing_abs_diff': 0.0035350509286997678, 'timing_relative_diff': 0.005050072755285381, 'duration_correct': True, 'duration_abs_diff': 5.551115123125783e-17, 'duration_relative_diff': 1.3877787807814457e-16}]\n", + "- stats:\n", + "{'pitch_all_aligned_correct': False, 'timing_all_correct': False, 'duration_all_correct': True, 'total_notes_in_reference': 5, 'total_notes_missing': 0, 'total_notes_extra': 0, 'total_notes_wrong_pitch': 1, 'total_notes_wrong_timing': 1, 'total_notes_wrong_duration': 0, 'total_notes_correct': 3, 'timing_scale': 1.2134811264230079, 'timing_offset': -0.05071899340922707, 'duration_scale': 1.1999999999999997, 'total_chords_in_reference': 2, 'total_chords_missing': 0, 'total_chords_extra': 0, 'total_chords_correct': 1, 'total_chords_imperfect': 1, 'total_chords_wrong': 0}\n" + ] + } + ], + "source": [ + "print(\"- is_correct:\")\n", + "print(result.is_correct)\n", + "print(\"- event details:\")\n", + "print(result.event_details)\n", + "print(\"- stats:\")\n", + "print(result.stats)" + ] + }, + { + "cell_type": "markdown", + "id": "f9c918e0", + "metadata": {}, + "source": [ + "## Possible future work" + ] + }, + { + "cell_type": "markdown", + "id": "71a6e28f", + "metadata": {}, + "source": [ + "visualise the feedback, maybe try the visualisation tool such as LilyPond, music21, or simply use matplotlib to plot PianoRoll." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "compareMusic", + "language": "python", + "name": "comparemusic" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/poetry.lock b/poetry.lock index f62ad4e..8852c54 100644 --- a/poetry.lock +++ b/poetry.lock @@ -432,6 +432,98 @@ files = [ ] markers = {main = "os_name == \"nt\" or sys_platform == \"win32\"", dev = "sys_platform == \"win32\""} +[[package]] +name = "contourpy" +version = "1.3.3" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.11" +groups = ["main"] +files = [ + {file = "contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1"}, + {file = "contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db"}, + {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620"}, + {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f"}, + {file = "contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff"}, + {file = "contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42"}, + {file = "contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470"}, + {file = "contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb"}, + {file = "contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1"}, + {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7"}, + {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411"}, + {file = "contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69"}, + {file = "contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b"}, + {file = "contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc"}, + {file = "contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5"}, + {file = "contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9"}, + {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659"}, + {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7"}, + {file = "contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d"}, + {file = "contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263"}, + {file = "contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9"}, + {file = "contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d"}, + {file = "contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b"}, + {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a"}, + {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e"}, + {file = "contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3"}, + {file = "contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8"}, + {file = "contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301"}, + {file = "contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a"}, + {file = "contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3"}, + {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b"}, + {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36"}, + {file = "contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d"}, + {file = "contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd"}, + {file = "contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339"}, + {file = "contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772"}, + {file = "contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0"}, + {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4"}, + {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f"}, + {file = "contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae"}, + {file = "contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc"}, + {file = "contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77"}, + {file = "contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880"}, +] + +[package.dependencies] +numpy = ">=1.25" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.17.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + [[package]] name = "crashtest" version = "0.4.1" @@ -507,6 +599,22 @@ cffi = {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy [package.extras] ssh = ["bcrypt (>=3.1.5)"] +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + [[package]] name = "distlib" version = "0.4.3" @@ -659,6 +767,79 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.12.0,<2.13.0" pyflakes = ">=3.2.0,<3.3.0" +[[package]] +name = "fonttools" +version = "4.63.0" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "fonttools-4.63.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b"}, + {file = "fonttools-4.63.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94"}, + {file = "fonttools-4.63.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579"}, + {file = "fonttools-4.63.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22"}, + {file = "fonttools-4.63.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e"}, + {file = "fonttools-4.63.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69"}, + {file = "fonttools-4.63.0-cp310-cp310-win32.whl", hash = "sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e"}, + {file = "fonttools-4.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac"}, + {file = "fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f"}, + {file = "fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9"}, + {file = "fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b"}, + {file = "fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18"}, + {file = "fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0"}, + {file = "fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007"}, + {file = "fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb"}, + {file = "fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c"}, + {file = "fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02"}, + {file = "fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0"}, + {file = "fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af"}, + {file = "fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8"}, + {file = "fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b"}, + {file = "fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78"}, + {file = "fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263"}, + {file = "fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272"}, + {file = "fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd"}, + {file = "fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59"}, + {file = "fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d"}, + {file = "fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68"}, + {file = "fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be"}, + {file = "fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27"}, + {file = "fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380"}, + {file = "fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b"}, + {file = "fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745"}, + {file = "fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03"}, + {file = "fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49"}, + {file = "fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b"}, + {file = "fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6"}, + {file = "fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4"}, + {file = "fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616"}, + {file = "fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5"}, + {file = "fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001"}, + {file = "fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e"}, + {file = "fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096"}, + {file = "fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f"}, + {file = "fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40"}, + {file = "fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196"}, + {file = "fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8"}, + {file = "fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419"}, + {file = "fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d"}, + {file = "fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0"}, +] + +[package.extras] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.45.0)", "unicodedata2 (>=17.0.0) ; python_version <= \"3.14\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.45.0)"] +symfont = ["sympy"] +type1 = ["xattr ; sys_platform == \"darwin\""] +unicode = ["unicodedata2 (>=17.0.0) ; python_version <= \"3.14\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] + [[package]] name = "h11" version = "0.16.0" @@ -961,6 +1142,133 @@ enabler = ["pytest-enabler (>=3.4)"] test = ["pyfakefs", "pytest (>=6,!=8.1.*)"] type = ["pygobject-stubs", "pytest-mypy (>=1.0.1)", "shtab", "types-pywin32"] +[[package]] +name = "kiwisolver" +version = "1.5.0" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374"}, + {file = "kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd"}, + {file = "kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede"}, + {file = "kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2"}, + {file = "kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875"}, + {file = "kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c"}, + {file = "kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb"}, + {file = "kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4"}, + {file = "kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b"}, + {file = "kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac"}, + {file = "kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9"}, + {file = "kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588"}, + {file = "kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9"}, + {file = "kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384"}, + {file = "kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7"}, + {file = "kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09"}, + {file = "kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3"}, + {file = "kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0"}, + {file = "kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276"}, + {file = "kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53"}, + {file = "kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615"}, + {file = "kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02"}, + {file = "kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796"}, + {file = "kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e"}, + {file = "kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1"}, + {file = "kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a"}, +] + [[package]] name = "lf-toolkit" version = "0.0.1" @@ -996,6 +1304,73 @@ url = "https://github.com/lambda-feedback/toolkit-python.git" reference = "main" resolved_reference = "713f13fec11a1d81668fefc3199942f604ba1505" +[[package]] +name = "matplotlib" +version = "3.11.0" +description = "Python plotting package" +optional = false +python-versions = ">=3.11" +groups = ["main"] +files = [ + {file = "matplotlib-3.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f857524b442f0f36e641868ce2171aafa88cb0bc0644f4e1d8a5df9b32649fef"}, + {file = "matplotlib-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:57baa92fdc82948ed716eae6d2579d4d6f40965cd8d2f416755b4a72580a3233"}, + {file = "matplotlib-3.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630eee0e67d35cce2019a0e670719f4816e3b86aff0fa72729f6c69786fceb45"}, + {file = "matplotlib-3.11.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5106c444d0bf966eee2853548c03772af4ab7199118e086c62fbac8ccb07c055"}, + {file = "matplotlib-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d7aea652b58e686444079be3376ef546bffa1eee9b9bb9c472b9fcf6cf410d3"}, + {file = "matplotlib-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:70a5b3e9a5dab708c0f039709ae7c68d5b4d254e291ef76492cdba230c8bb5e4"}, + {file = "matplotlib-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:3d68266213e73823ac3be90615bab0cf31f88851e114cdb1dd25dacf3b01e1a7"}, + {file = "matplotlib-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06b5872e9cf11adc8f589ded3ce11bc3e1061ad498259664fabc1f6615beb918"}, + {file = "matplotlib-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0515d495124be3124340e59f164d901ed4484e2246a5b74cfa483cac3b80bd97"}, + {file = "matplotlib-3.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be5f93a1d21981bfb802ded0d77a0caa92d4342a47d45754fac77e314a506344"}, + {file = "matplotlib-3.11.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41635d7909d19e52e924a521dde6d8f670b0f53ab1d0e8c331fa831554f681d1"}, + {file = "matplotlib-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94f5000f67ca9faa300863ea17f8bce9175cb67b88bec4bc7780502d53dd7c9e"}, + {file = "matplotlib-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac6f1ef39f3d0f9e2463303013094992cdbe0f85f43bc54155bc472b2042768e"}, + {file = "matplotlib-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:9dd11fb612ce7bc60b1de5b4fc87ff959d22317b5de42aabf392f66f97af22eb"}, + {file = "matplotlib-3.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce3b839b34ae1f430b4616893a2945a2999debaa7e94e7e29a2a8bbf286f7b5"}, + {file = "matplotlib-3.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:373db8f91214e8ccaf35ac833cc1dd59dd961e148bbd55dd027141591dde1313"}, + {file = "matplotlib-3.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be152b7570324dc8d01574cc9474dd2d803237acf528bcbb5b211fa347461a09"}, + {file = "matplotlib-3.11.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:126f256df600652d7e4b394cf3164ff75210a00038f287c95a012a6f58d0e83f"}, + {file = "matplotlib-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:03acfeddf87b0dddb11b081ef7740ad445a3ca8bcb6b8e3011b08f2cf802b75c"}, + {file = "matplotlib-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:ab3722f04f3ff34c23b5012c5873d2894174e06c3822fcdac3610965a5ac7d06"}, + {file = "matplotlib-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c945824670fb8915b4ac879e5e61f3c58e0913022f70a0de4c082b17372f8771"}, + {file = "matplotlib-3.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3489c3dc487669b4a980bc3068f87856de7a1564248d3f6c629efb2a58b03f24"}, + {file = "matplotlib-3.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6a98f5476ce784a50ce09998f4ae1e6a9f25043cef8a480c98949902eda74620"}, + {file = "matplotlib-3.11.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:565af866fd63e4bd3f987d580afe27c44c2552a3b3305f4ecbb85133601ea6f3"}, + {file = "matplotlib-3.11.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6b3e64dea5062c570f04358e2711859f3531b459f29516274fbad889079e4f3"}, + {file = "matplotlib-3.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:942b37c5db1899610bd1543ce8e13e4ecff9a4633e7f63bb6aa9205d2644ebd1"}, + {file = "matplotlib-3.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c08e649a6313e1291e713623b97a38e5bb4aa580b2a100a94a3309bc6b9c8eb3"}, + {file = "matplotlib-3.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2746cd2c113742ff6ce37a864c5ac5fd7aa644568f445e66166e457ac78e40e0"}, + {file = "matplotlib-3.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3338e3e3de128cf50d0d2fb92a122815daf9c755bd882a474343c05f8fd7ec79"}, + {file = "matplotlib-3.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:25c2e5455efd8d99f41fb79871a31feb7d301569642e332ec58d72cfe9282bc3"}, + {file = "matplotlib-3.11.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9695457a467ff86d23f35037a43deb6f1134dd6d3e2ac8ce1e2087cff09ffb9"}, + {file = "matplotlib-3.11.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19c16c61dea63b3582918503e6b294193961261d9daa806d4ae2151f1ad05430"}, + {file = "matplotlib-3.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2d72ea8b7924f3cb955e61518d21e43b3df1e6c8a793b480a0c1214f185d30ba"}, + {file = "matplotlib-3.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:1c02da0a629dfa9debf52725ea06866b74c1fb70a895bae05e4493d34074f9f2"}, + {file = "matplotlib-3.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aa55d73b3117d4b07f959cd9eb6f69b375d8df3414139c479388e551aa5d999d"}, + {file = "matplotlib-3.11.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9d8c6e7cd2f0ddf11d8d92e520dd1d9d2abb0cf6ac8831e338666c81e905847"}, + {file = "matplotlib-3.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:be050fcf32f729eda99f7f75a80bf67612ce16ab9ac1c23a387dcaede95cb70e"}, + {file = "matplotlib-3.11.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfabef0230d0697aa0d717385194dd41162e00207a68bf4abf94c2bf4c27dca0"}, + {file = "matplotlib-3.11.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1644db30e759199443493ac5e5caec24fdb775a8f6123021f85ba47c4133c3cb"}, + {file = "matplotlib-3.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15b0d160079cb10699a0e98b5989c70677b2df7cacdc62af67c30f2facec46d9"}, + {file = "matplotlib-3.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:446307e6b04b57b1f1239e228a1ec2af0d589a1008cebc3dfa3f5441d095cfb6"}, + {file = "matplotlib-3.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:652fb5696271d4c50f196d22a5ff4f8e4444c74f847423570d7dc0aa2bbd0159"}, + {file = "matplotlib-3.11.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81ae77077a1e16d37a5b61096ccb07c8d90a99b518fa8256b8f21578932f2f62"}, + {file = "matplotlib-3.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ddef37840695f5eef65f9f070fe2d2f510f584c2156203f9f622a5b0584efffd"}, + {file = "matplotlib-3.11.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf662e5ac5707658cb931e19972c4bd99f7b4f8b7bf79d3c821d239fa6b71e64"}, + {file = "matplotlib-3.11.0.tar.gz", hash = "sha256:68c0c7be01b30dcca3638934f7f591df73401235cbdbf0d1ab1c71e7db7f8b57"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.25" +packaging = ">=20.0" +pillow = ">=9" +pyparsing = ">=3" +python-dateutil = ">=2.7" + [[package]] name = "mccabe" version = "0.7.0" @@ -1631,6 +2006,21 @@ files = [ {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, ] +[[package]] +name = "pyparsing" +version = "3.3.2" +description = "pyparsing - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d"}, + {file = "pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "pyproject-hooks" version = "1.2.0" @@ -2481,4 +2871,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = "^3.11" -content-hash = "2ca77bf0f8acc4673958514a8043a5d543bd6029389a4130956cb33a2b21fc63" +content-hash = "494427295e39d085dd9aaa6680cb28ad115685b73a2cbf41116fca463abc54ee" diff --git a/pyproject.toml b/pyproject.toml index 91c9adc..2928e06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ lf_toolkit = { git = "https://github.com/lambda-feedback/toolkit-python.git", br "ipc", ] } numpy = "^2.4.6" +matplotlib = "^3.11.0" [tool.poetry.group.dev.dependencies] pytest = "^8.2.2"