diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..f9ecf57
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,7 @@
+version: 2
+updates:
+ # Maintain dependencies for GitHub Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "monthly"
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..5709454
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,60 @@
+name: Build and Deploy Book
+
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - main
+
+env:
+ BASE_URL: /${{ github.event.repository.name }}
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build-book:
+ name: Build Book
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ # Required for miniconda to activate conda
+ defaults:
+ run:
+ shell: bash -l {0}
+ steps:
+ - name: Check out repo
+ uses: actions/checkout@v4
+ - name: Get conda
+ uses: conda-incubator/setup-miniconda@v3
+ with:
+ auto-update-conda: true
+ python-version: "3.12"
+ miniforge-version: latest
+ use-mamba: true
+ environment-file: environment.yml
+ activate-environment: skhep-tutorial
+ - name: Build the book
+ run: |
+ jupyter-book build scikit_hep_tutorial
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: "scikit_hep_tutorial/_build/html"
+
+ deploy-book:
+ name: Deploy Book
+ runs-on: ubuntu-latest
+ needs: build-book
+ if: github.event_name == 'push'
+ permissions:
+ pages: write
+ id-token: write
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..70bd6dd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+_build
+.ipynb_*
+*.root
+
+.DS_Store
+
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..ee1e0ca
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,29 @@
+ci:
+ autoupdate_schedule: monthly
+
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v5.0.0
+ hooks:
+ - id: check-added-large-files
+ - id: check-case-conflict
+ - id: check-merge-conflict
+ - id: check-symlinks
+ - id: debug-statements
+ - id: end-of-file-fixer
+ files: ^(_episodes|README.md|setup.md)
+ - id: mixed-line-ending
+ - id: trailing-whitespace
+ files: ^(_episodes|code|README.md|setup.md)
+
+ - repo: https://github.com/asottile/blacken-docs
+ rev: 1.19.0
+ hooks:
+ - id: blacken-docs
+ additional_dependencies: [black==22.6.0]
+ files: '.*\.md'
+
+ - repo: https://github.com/kynan/nbstripout
+ rev: 0.7.1
+ hooks:
+ - id: nbstripout
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..2784237
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,84 @@
+# Código de Conducta convenido para Contribuyentes
+
+## Nuestro compromiso
+
+Nosotros, como miembros, contribuyentes y administradores nos comprometemos a hacer de la participación en nuestra comunidad sea una experiencia libre de acoso para todo el mundo, independientemente de la edad, dimensión corporal, discapacidad visible o invisible, etnicidad, características sexuales, identidad y expresión de género, nivel de experiencia, educación, nivel socio-económico, nacionalidad, apariencia personal, raza, casta, color, religión, o identidad u orientación sexual.
+
+Nos comprometemos a actuar e interactuar de maneras que contribuyan a una comunidad abierta, acogedora, diversa, inclusiva y sana.
+
+## Nuestros estándares
+
+Ejemplos de comportamientos que contribuyen a crear un ambiente positivo para nuestra comunidad:
+
+* Demostrar empatía y amabilidad ante otras personas
+* Respeto a diferentes opiniones, puntos de vista y experiencias
+* Dar y aceptar adecuadamente retroalimentación constructiva
+* Aceptar la responsabilidad y disculparse ante quienes se vean afectados por nuestros errores, aprendiendo de la experiencia
+* Centrarse en lo que sea mejor no sólo para nosotros como individuos, sino para la comunidad en general
+
+Ejemplos de comportamiento inaceptable:
+
+* El uso de lenguaje o imágenes sexualizadas, y aproximaciones o
+ atenciones sexuales de cualquier tipo
+* Comentarios despectivos (_trolling_), insultantes o derogatorios, y ataques personales o políticos
+* El acoso en público o privado
+* Publicar información privada de otras personas, tales como direcciones físicas o de correo
+ electrónico, sin su permiso explícito
+* Otras conductas que puedan ser razonablemente consideradas como inapropiadas en un
+ entorno profesional
+
+## Aplicación de las responsabilidades
+
+Los administradores de la comunidad son responsables de aclarar y hacer cumplir nuestros estándares de comportamiento aceptable y tomarán acciones apropiadas y correctivas de forma justa en respuesta a cualquier comportamiento que consideren inapropiado, amenazante, ofensivo o dañino.
+
+Los administradores de la comunidad tendrán el derecho y la responsabilidad de eliminar, editar o rechazar comentarios, _commits_, código, ediciones de páginas de wiki, _issues_ y otras contribuciones que no se alineen con este Código de Conducta, y comunicarán las razones para sus decisiones de moderación cuando sea apropiado.
+
+## Alcance
+
+Este código de conducta aplica tanto a espacios del proyecto como a espacios públicos donde un individuo esté en representación del proyecto o comunidad. Ejemplos de esto incluyen el uso de la cuenta oficial de correo electrónico, publicaciones a través de las redes sociales oficiales, o presentaciones con personas designadas en eventos en línea o no.
+
+## Aplicación
+
+Instancias de comportamiento abusivo, acosador o inaceptable de otro modo podrán ser reportadas a los administradores de la comunidad responsables del cumplimiento a través de [INSERTAR MÉTODO DE CONTACTO]. Todas las quejas serán evaluadas e investigadas de una manera puntual y justa.
+
+Todos los administradores de la comunidad están obligados a respetar la privacidad y la seguridad de quienes reporten incidentes.
+
+## Guías de Aplicación
+
+Los administradores de la comunidad seguirán estas Guías de Impacto en la Comunidad para determinar las consecuencias de cualquier acción que juzguen como un incumplimiento de este Código de Conducta:
+
+### 1. Corrección
+
+**Impacto en la Comunidad**: El uso de lenguaje inapropiado u otro comportamiento considerado no profesional o no acogedor en la comunidad.
+
+**Consecuencia**: Un aviso escrito y privado por parte de los administradores de la comunidad, proporcionando claridad alrededor de la naturaleza de este incumplimiento y una explicación de por qué el comportamiento es inaceptable. Una disculpa pública podría ser solicitada.
+
+### 2. Aviso
+
+**Impacto en la Comunidad**: Un incumplimiento causado por un único incidente o por una cadena de acciones.
+
+**Consecuencia**: Un aviso con consecuencias por comportamiento prolongado. No se interactúa con las personas involucradas, incluyendo interacción no solicitada con quienes se encuentran aplicando el Código de Conducta, por un periodo especificado de tiempo. Esto incluye evitar las interacciones en espacios de la comunidad, así como a través de canales externos como las redes sociales. Incumplir estos términos puede conducir a una expulsión temporal o permanente.
+
+### 3. Expulsión temporal
+
+**Impacto en la Comunidad**: Una serie de incumplimientos de los estándares de la comunidad, incluyendo comportamiento inapropiado continuo.
+
+**Consecuencia**: Una expulsión temporal de cualquier forma de interacción o comunicación pública con la comunidad durante un intervalo de tiempo especificado. No se permite interactuar de manera pública o privada con las personas involucradas, incluyendo interacciones no solicitadas con quienes se encuentran aplicando el Código de Conducta, durante este periodo. Incumplir estos términos puede conducir a una expulsión permanente.
+
+### 4. Expulsión permanente
+
+**Impacto en la Comunidad**: Demostrar un patrón sistemático de incumplimientos de los estándares de la comunidad, incluyendo conductas inapropiadas prolongadas en el tiempo, acoso de individuos, o agresiones o menosprecio a grupos de individuos.
+
+**Consecuencia**: Una expulsión permanente de cualquier tipo de interacción pública con la comunidad del proyecto.
+
+## Atribución
+
+Este Código de Conducta es una adaptación del [Contributor Covenant][homepage], versión 2.1,
+disponible en https://www.contributor-covenant.org/es/version/2/1/code_of_conduct.html
+
+Las Guías de Impacto en la Comunidad están inspiradas en la [escalera de aplicación del código de conducta de Mozilla](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+Para respuestas a las preguntas frecuentes de este código de conducta, consulta las FAQ en
+https://www.contributor-covenant.org/faq. Hay traducciones disponibles en https://www.contributor-covenant.org/translations
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..db2a926
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,105 @@
+# Contribuyendo
+
+[HSF training][hsf-training] es un proyecto de código abierto,
+y damos la bienvenida a contribuciones de todo tipo:
+nuevas lecciones,
+correcciones de material existente,
+informes de errores,
+y revisiones de los cambios propuestos, todos son bienvenidos.
+
+## Acuerdo de Contribución
+
+Al contribuir,
+aceptas que podemos redistribuir tu trabajo bajo [nuestra licencia](LICENSE.md).
+A cambio,
+abordaremos tus problemas y/o evaluaremos tu propuesta de cambio lo más pronto posible,
+y te ayudaremos a convertirte en miembro de nuestra comunidad.
+Todos los involucrados en [HSF training][hsf-training]
+aceptan cumplir con nuestro [código de conducta](CODE_OF_CONDUCT.md).
+
+## Cómo Contribuir
+
+La forma más sencilla de empezar es presentar un "issue"
+para informarnos sobre un error,
+una redacción incómoda,
+o un error factual.
+Esta es una buena manera de presentarte
+y conocer a algunos de los miembros de nuestra comunidad.
+
+1. Si no tienes una cuenta en [GitHub][github],
+ puedes escribir un correo electrónico a los coordinadores.
+ Sin embargo,
+ podremos responder más rápidamente si utilizas uno de los métodos descritos a continuación.
+
+2. Si tienes una cuenta en [GitHub][github],
+ o estás dispuesto a [crear una][github-join],
+ pero no sabes cómo usar Git,
+ puedes reportar problemas o sugerir mejoras [creando un issue][issues].
+ Esto nos permite asignar el elemento a alguien
+ y responder a él en una discusión encadenada.
+
+3. Si te sientes cómodo con Git,
+ y te gustaría agregar o cambiar material,
+ puedes enviar un "pull request" (PR).
+ Las instrucciones para hacerlo están [incluidas a continuación](#using-github).
+
+## Qué Contribuir
+
+Hay muchas formas de contribuir,
+desde escribir nuevos ejercicios y mejorar los existentes,
+hasta actualizar o completar la documentación
+y enviar [informes de errores][issues]
+sobre cosas que no funcionan, no están claras o están ausentes.
+Si buscas ideas, consulta la pestaña 'Issues' para ver
+una lista de problemas asociados a este repositorio,
+o también puedes revisar todos los problemas en [hsf-training][hsf-training-issues].
+
+También hay [una lista](hsf-training-gfis) de todos los problemas que son particularmente fáciles y adecuados
+para las primeras contribuciones.
+
+Los comentarios sobre issues y las revisiones de pull requests son igualmente bienvenidos:
+somos más inteligentes juntos que por separado.
+Las revisiones de principiantes y recién llegados son especialmente valiosas:
+es fácil para quienes han estado utilizando estas lecciones durante un tiempo
+olvidar lo impenetrable que puede ser parte de este material,
+por lo que siempre se agradecen ojos nuevos.
+
+## Usando GitHub
+
+Si decides contribuir a través de GitHub, es posible que desees consultar
+[Cómo Contribuir a un Proyecto de Código Abierto en GitHub][how-contribute].
+Para gestionar cambios, seguimos [el flujo de GitHub][github-flow].
+Cada lección tiene dos mantenedores que revisan problemas y pull requests o animan a otros a hacerlo.
+Los mantenedores son voluntarios de la comunidad y tienen la última palabra sobre lo que se fusiona en la lección.
+Para usar la interfaz web para contribuir a una lección:
+
+1. Haz un fork del repositorio original a tu perfil de GitHub.
+2. Dentro de tu versión del repositorio forked, muévete a la rama `main` y
+ crea una nueva rama para cada cambio significativo que se realice.
+3. Navega a los archivos que deseas cambiar dentro de las nuevas ramas y realiza las revisiones necesarias.
+4. Confirma todos los archivos cambiados dentro de las ramas correspondientes.
+5. Crea pull requests individuales desde cada una de tus ramas modificadas
+ a la rama `main` dentro del repositorio original.
+6. Si recibes comentarios, realiza cambios utilizando tus ramas específicas del problema del repositorio forked y
+ los pull requests se actualizarán automáticamente.
+7. Repite según sea necesario hasta que se haya abordado todo el feedback.
+
+Al comenzar a trabajar, asegúrate de que tu clon de la rama `main` original esté actualizado
+antes de crear tus propias ramas específicas de revisión a partir de allí.
+Además, por favor, trabaja solo desde tus ramas recién creadas y *no*
+desde tu clon de la rama `main` original.
+Por último, las copias publicadas de todas las lecciones están disponibles en la rama `main` del repositorio original para referencia mientras revisas.
+
+## Otros Recursos
+
+Más información sobre cómo contribuir o cómo contactarnos: [Inicio de HSF training][hsf-training]
+
+[hsf-training-issues]: https://github.com/issues?q=user%3Ahsf-training+is%3Aopen
+[hsf-training]: https://hepsoftwarefoundation.org/workinggroups/training.html
+[email]: mailto:https://groups.google.com/forum/#!forum/hsf-training-wg
+[github]: https://github.com
+[github-flow]: https://guides.github.com/introduction/flow/
+[github-join]: https://github.com/join
+[how-contribute]: https://docs.github.com/en/get-started/quickstart/contributing-to-projects
+[issues]: https://guides.github.com/features/issues/
+[hsf-training-gfis]: https://github.com/issues?q=is%3Aissue+is%3Aopen+archived%3Afalse+sort%3Aupdated-desc+label%3A%22good+first+issue%22+org%3Ahsf-training
diff --git a/LICENSE b/LICENSE
new file mode 100755
index 0000000..4ea99c2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,395 @@
+Attribution 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution 4.0 International Public License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution 4.0 International Public License ("Public License"). To the
+extent this Public License may be interpreted as a contract, You are
+granted the Licensed Rights in consideration of Your acceptance of
+these terms and conditions, and the Licensor grants You such rights in
+consideration of benefits the Licensor receives from making the
+Licensed Material available under these terms and conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ d. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ e. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ f. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ g. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ h. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ i. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ j. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ k. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ 4. If You Share Adapted Material You produce, the Adapter's
+ License You apply must not prevent recipients of the Adapted
+ Material from complying with this Public License.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material; and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0d6fe34
--- /dev/null
+++ b/README.md
@@ -0,0 +1,35 @@
+[![HSF Training Center](https://img.shields.io/badge/HSF%20Training%20Center-browse-ff69b4)](https://hepsoftwarefoundation.org/training/curriculum.html)
+# Tutorial de Scikit-HEP
+
+Este es un tutorial sobre [Scikit-HEP](https://scikit-hep.org), una colección de paquetes para el análisis de física de partículas en Python.
+
+![ecosistema scikit hep](scikit_hep_tutorial/fig/scikit-hep-logos.png)
+
+Fue escrito por [Jim Pivarski][jpivarski] (originalmente en inglés [aquí](https://hsf-training.github.io/hsf-training-scikit-hep-webpage/index.html), traducido por [Andrés Ríos Tascón](https://github.com/ariostas)) y se enseñó por primera vez durante un [Taller de Software Carpentry](https://indico.cern.ch/event/1097111/timetable/#day-2021-12-15) el 15 de diciembre de 2021. La versión original en inglés se puede encontrar [aquí](https://github.com/hsf-training/hsf-training-scikit-hep-webpage).
+
+¡Este repositorio genera una página web que está disponible [aquí](https://hsf-training.github.io/hsf-training-scikit-hep-webpage/)!
+
+El tutorial de Scikit-HEP es parte del [currículo de formación del HSF](https://hepsoftwarefoundation.org/training/curriculum).
+
+## Contribuciones
+
+¡Damos la bienvenida a todas las contribuciones para mejorar el tutorial! Los mantenedores harán todo lo posible por ayudarte si tienes alguna pregunta, inquietud o experimentas alguna dificultad durante el proceso.
+
+Nos gustaría pedirte que te familiarices con nuestra [Guía de Contribuciones](CONTRIBUTING.md) y revises las [directrices más detalladas][lesson-example] sobre el formato adecuado, cómo renderizar la lección localmente e incluso cómo escribir nuevos episodios.
+
+## Mantenedores
+
+* [Andres Rios Tascon][ariostas]
+
+## Autores
+
+Una lista de los colaboradores de la lección se puede encontrar en [AUTORES](AUTHORS).
+
+## Citación
+
+Para citar este tutorial, consulta con [CITATION](CITATION).
+
+[lesson-example]: https://carpentries.github.io/lesson-example
+[jpivarski]: https://github.com/jpivarski/
+[carpentries]: https://software-carpentry.org/
+[ariostas]: https://github.com/ariostas/
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 0000000..29a661a
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,20 @@
+name: skhep-tutorial
+channels:
+ - conda-forge
+ - defaults
+dependencies:
+ - jupyter-book=1.0.3
+ - uproot=5.4.1
+ - xrootd=5.7.1
+ - fsspec-xrootd=0.4.0
+ - awkward=2.6.9
+ - hist=2.8.0
+ - scikit-hep-testdata=0.4.48
+ - matplotlib=3.9.2
+ - particle=0.25.2
+ - vector=1.5.2
+ - pandas=2.2.3
+ - requests=2.32.3
+ - aiohttp=3.10.10
+ - python=3.12
+ - fastjet=3.4.0.6
diff --git a/scikit_hep_tutorial/00-bienvenida.md b/scikit_hep_tutorial/00-bienvenida.md
new file mode 100644
index 0000000..0371abe
--- /dev/null
+++ b/scikit_hep_tutorial/00-bienvenida.md
@@ -0,0 +1,12 @@
+# ¡Bienvenido/a!
+
+Este tutorial tiene como objetivo demostrar cómo comenzar rápidamente con [Scikit-HEP](https://scikit-hep.org), una colección de paquetes para el análisis de física de partículas en Python.
+
+El tutorial fue escrito por [Jim Pivarski](https://github.com/jpivarski/) (originalmente en inglés [aquí](https://hsf-training.github.io/hsf-training-scikit-hep-webpage/index.html), traducido por [Andrés Ríos Tascón](https://github.com/ariostas)) y fue enseñado por primera vez durante un [Taller de Software Carpentry](https://indico.cern.ch/event/1097111/timetable/#day-2021-12-15) el 15 de diciembre de 2021.
+
+```{admonition} Prerrequisitos
+Conocimiento básico de Python, por ejemplo, a través de la lección [Análisis y visualización de datos usando Python](https://datacarpentry.org/python-ecology-lesson-es/)
+```
+
+```{tableofcontents}
+```
\ No newline at end of file
diff --git a/scikit_hep_tutorial/01-introduccion.ipynb b/scikit_hep_tutorial/01-introduccion.ipynb
new file mode 100644
index 0000000..0639cfa
--- /dev/null
+++ b/scikit_hep_tutorial/01-introduccion.ipynb
@@ -0,0 +1,700 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": [
+ "# Introducción: Fundamentos de Python"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## ¿Cuánto Python necesitamos saber?\n",
+ "\n",
+ "### Python básico\n",
+ "\n",
+ "La mayoría de los estudiantes comienzan este módulo después de una introducción a Python o ya están familiarizados con los *fundamentos* de Python. Por ejemplo, deberías sentirte cómodo con lo esencial de Python, como asignar variables,\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x = 5"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "sentencias if,"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "if x < 5:\n",
+ " print(\"pequeño\")\n",
+ "else:\n",
+ " print(\"grande\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5",
+ "metadata": {},
+ "source": [
+ "y bucles."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for i in range(x):\n",
+ " print(i)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7",
+ "metadata": {},
+ "source": [
+ "Tu análisis de datos probablemente estará lleno de declaraciones como estas, aunque los tipos de operaciones en las que nos enfocaremos en este módulo tendrán una forma más parecida a\n",
+ "\n",
+ "```python\n",
+ "import libreria_compilada\n",
+ "\n",
+ "libreria_compilata.operacion_computacional_costosa(array_grande)\n",
+ "```\n",
+ "\n",
+ "El truco está en que el código del lado de Python sea lo suficientemente expresivo y el código compilado lo suficientemente general para que no necesites una nueva `libreria_compilada` para cada cosa que quieras hacer. Las librería presentadas en este módulo están diseñadas con interfaces que te permiten expresar lo que quieres hacer en Python y ejecutarlo en código compilado."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8",
+ "metadata": {},
+ "source": [
+ "### Interfaces tipo diccionario y tipo array\n",
+ "\n",
+ "Los dos tipos de datos más importantes para estas interfaces son los diccionarios"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_diccionario = {\"palabra\": 1, \"otra palabra\": 2, \"alguna otra palabra\": 3}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "10",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_diccionario[\"alguna otra palabra\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "11",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for clave in un_diccionario:\n",
+ " print(clave, un_diccionario[clave])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12",
+ "metadata": {},
+ "source": [
+ "y los arrays"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "13",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "un_array = np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "14",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_array[4]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "15",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for x in un_array:\n",
+ " print(x)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "16",
+ "metadata": {},
+ "source": [
+ "Aunque estos tipos de datos son importantes, lo que es más importante son las interfaces de estos tipos. Ambos representan funciones de claves a valores:\n",
+ "\n",
+ "- los diccionarios (usualmente) mapean de cadenas a objetos de Python,\n",
+ "- los arrays (siempre) mapean de enteros no negativos a valores numéricos.\n",
+ "\n",
+ "La mayoría de los tipos que veremos en este módulo también mapean cadenas o enteros a datos, y usan la misma sintaxis que los diccionarios y los arrays. Si estás familiarizado con las interfaces de diccionarios y arrays, generalmente no tendrás que consultar la documentación sobre tipos parecidos a diccionarios o arrays, a menos que estés tratando de hacer algo especial.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17",
+ "metadata": {},
+ "source": [
+ "### Repaso de la interfaz de diccionario\n",
+ "\n",
+ "Para los diccionarios, las cosas que pueden ir entre corchetes (su \"dominio,\" como una función) son sus claves (`keys`)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "18",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_diccionario = {\"palabra\": 1, \"otra palabra\": 2, \"alguna otra palabra\": 3}\n",
+ "un_diccionario.keys()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "19",
+ "metadata": {},
+ "source": [
+ "Nada más que estas claves puede usarse entre corchetes."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "20",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_diccionario[\"algo que inventé\"]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "21",
+ "metadata": {},
+ "source": [
+ "a menos que haya sido agregado al diccionario."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "22",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_diccionario[\"algo que inventé\"] = 123\n",
+ "un_diccionario[\"algo que inventé\"]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "23",
+ "metadata": {},
+ "source": [
+ "Los elementos que pueden salir de un diccionario (su \"rango,\" como una función) son sus valores (`values`)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "24",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_diccionario.values()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25",
+ "metadata": {},
+ "source": [
+ "Puedes obtener las claves y los valores como tuplas de 2 elementos al solicitar sus artículos (`items`)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "26",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_diccionario.items()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "27",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for clave, valor in un_diccionario.items():\n",
+ " print(clave, valor)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "28",
+ "metadata": {},
+ "source": [
+ "### Repaso de la interfaz de array\n",
+ "\n",
+ "Para los arrays, las cosas que pueden ir entre corchetes (su \"dominio,\" como una función) son enteros desde cero hasta, pero sin incluir, su longitud."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "29",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "un_array = np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9])\n",
+ "un_array[0] # está bien\n",
+ "un_array[1] # está bien\n",
+ "un_array[9] # está bien\n",
+ "un_array[10] # no está bien"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "30",
+ "metadata": {},
+ "source": [
+ "Puedes obtener la longitud de un array (y el número de claves en un diccionario) con `len`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "31",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "len(un_array)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "32",
+ "metadata": {},
+ "source": [
+ "Es importante recordar que el índice `0` corresponde al primer elemento del array, `1` al segundo, y así sucesivamente, por lo que `len(some_array)` no es un índice válido.\n",
+ "\n",
+ "Se permiten índices negativos, pero cuentan desde el final de la lista hacia el principio. Por ejemplo,"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "33",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_array[-1]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "34",
+ "metadata": {},
+ "source": [
+ "devuelve el último elemento.\n",
+ "\n",
+ "**Quiz rápido:** ¿qué valor negativo devuelve el primer elemento, equivalente a `0`?\n",
+ "\n",
+ "Los arrays también se pueden rebanar o partir colocando dos puntos (`:`) entre el índice inicial y el índice final."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "35",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_array[2:7]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "36",
+ "metadata": {},
+ "source": [
+ "**Quiz rápido:** ¿por qué `7.7` no está incluido?\n",
+ "\n",
+ "Lo anterior es común a todas las secuencias de Python. Sin embargo, los arrays pueden ser multidimensionales y esto permite más tipos de rebanado."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "37",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "array3d = np.arange(2 * 3 * 5).reshape(2, 3, 5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "38",
+ "metadata": {},
+ "source": [
+ "Separando dos rebanadas en los corchetes con una coma"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "39",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "array3d[:, 1:, 1:]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "40",
+ "metadata": {},
+ "source": [
+ "selecciona lo siguiente:\n",
+ "\n",
+ "![array3d-highlight1](fig/array3d-highlight1.png)\n",
+ "\n",
+ "**Quiz rápido:** ¿cómo seleccionas lo siguiente?\n",
+ "\n",
+ "![array3d-highlight2](fig/array3d-highlight2.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "41",
+ "metadata": {},
+ "source": [
+ "### Filtrado con booleanos y enteros: \"cortes\"\n",
+ "\n",
+ "Además de enteros y rebanadas, los arrays pueden incluirse en los corchetes.\n",
+ "\n",
+ "Un array de booleanos con la misma longitud que el array rebanado selecciona todos los elementos que coinciden con verdadero (`True`)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "42",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_array = np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9])\n",
+ "array_booleano = np.array(\n",
+ " [True, True, True, True, True, False, True, False, True, False]\n",
+ ")\n",
+ "\n",
+ "un_array[array_booleano]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "43",
+ "metadata": {},
+ "source": [
+ "Un array de enteros selecciona elementos por índice."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "44",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_array = np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9])\n",
+ "array_de_enteros = np.array([0, 1, 2, 3, 4, 6, 8])\n",
+ "\n",
+ "un_array[array_de_enteros]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "45",
+ "metadata": {},
+ "source": [
+ "El rebanado por enteros es más general que el rebanado booleano porque un array de enteros también puede cambiar el orden de los datos y repetir elementos."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "46",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "un_array[np.array([4, 2, 2, 2, 9, 8, 3])]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "47",
+ "metadata": {},
+ "source": [
+ "Ambos aparecen en contextos naturales. Los arrays booleanos a menudo provienen de realizar un cálculo en todos los elementos de un array que devuelve valores booleanos."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "48",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "elementos_pares = un_array * 10 % 2 == 0\n",
+ "\n",
+ "un_array[elementos_pares]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "49",
+ "metadata": {},
+ "source": [
+ "Así es como estaremos calculando y aplicando cortes: expresiones como\n",
+ "\n",
+ "```python\n",
+ "corte_muon_bueno = (muones.pt > 10) & (abs(muones.eta) < 2.4)\n",
+ "\n",
+ "muones_buenos = muones[corte_muon_bueno]\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "50",
+ "metadata": {},
+ "source": [
+ "### Operadores lógicos: `&`, `|`, `~`, y paréntesis\n",
+ "\n",
+ "Si vienes de C++, podrías esperar que \"y,\" \"o,\" \"no\" sean `&&`, `||`, `!`.\n",
+ "\n",
+ "Si vienes de Python no orientado a arrays, podrías esperar que sean `and`, `or`, `not`.\n",
+ "\n",
+ "En las expresiones de arrays (¡desafortunadamente!), tenemos que usar los operadores bit a bit de Python, `&`, `|`, `~`, y asegurarnos de que las comparaciones estén rodeadas de paréntesis. Los operadores `and`, `or`, `not` de Python no se aplican a través de arrays y los operadores bit a bit tienen una precedencia de operadores sorprendente.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "51",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x = 0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "52",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x > -10 & x < 10 # ¡probablemente no es lo que esperas!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "53",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "(x > -10) & (x < 10)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "54",
+ "metadata": {},
+ "source": [
+ "![bitwise-operator-parentheses](fig/bitwise-operator-parentheses.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "55",
+ "metadata": {},
+ "source": [
+ "## ¿Qué es el procesamiento \"orientado a arrays\" o \"columnar\"?\n",
+ "\n",
+ "Expresiones como"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "56",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "elementos_pares = un_array * 10 % 2 == 0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "57",
+ "metadata": {},
+ "source": [
+ "realizan las operaciones `*`, `%`, y `==` en cada elemento de `un_array` y devuelven arrays. Sin NumPy, lo anterior tendría que escribirse como"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "58",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "elementos_pares = []\n",
+ "\n",
+ "for x in un_array:\n",
+ " elementos_pares.append(x * 10 % 2 == 0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "59",
+ "metadata": {},
+ "source": [
+ "Esto es más engorroso cuando deseas aplicar una fórmula matemática a cada elemento de una colección, pero también es considerablemente más lento. Cada paso en un bucle `for` en Python realiza verificaciones de sanidad que son innecesarias para valores numéricos de tipo uniforme, verificaciones que se harían en tiempo de compilación en una librería compilada. NumPy *es* una librería compilada; sus operadores `*`, `%`, y `==`, así como muchas otras funciones, se realizan en bucles rápidos y compilados.\n",
+ "\n",
+ "Así es como obtenemos expresividad y velocidad. Los lenguajes con operadores que se aplican a arrays completos, en lugar de un valor escalar a la vez, se llaman lenguajes \"orientados a arrays\" o \"columnares\" (refiriéndose, por ejemplo, a las columnas de un DataFrame de Pandas).\n",
+ "\n",
+ "Bastantes lenguajes interactivos de análisis de datos están orientados a arrays, derivando del APL original. \"Orientado a arrays\" es un paradigma de programación en el mismo sentido que \"funcional\" u \"orientado a objetos\".\n",
+ "\n",
+ "![apl-timeline](fig/apl-timeline.png)\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "60",
+ "metadata": {},
+ "source": [
+ "## ¿Qué es un \"ecosistema de software\"?\n",
+ "\n",
+ "Algunos entornos de programación, como Mathematica, Emacs y ROOT, intentan proporcionarte todo lo que necesitas en un solo paquete. Solo hay una cosa que instalar y los componentes dentro del marco deberían funcionar bien juntos porque se desarrollaron en conjunto. Sin embargo, puede ser difícil usar el marco con otros paquetes de software no relacionados.\n",
+ "\n",
+ "Los ecosistemas, como UNIX, la App Store de iOS y Python, consisten en muchos paquetes de software pequeños que hacen una sola cosa y saben cómo comunicarse con otros paquetes a través de convenciones y protocolos. Por lo general, hay un mecanismo de instalación centralizado, y es responsabilidad del usuario reunir lo que se necesita. Sin embargo, el conjunto de posibilidades es abierto y crece a medida que surgen nuevas necesidades.\n",
+ "\n",
+ "En el Python mainstream, esto significa que\n",
+ "\n",
+ "- NumPy *solo* maneja arrays,\n",
+ "- Pandas *solo* maneja tablas,\n",
+ "- Matplotlib *solo* genera gráficos,\n",
+ "- Jupyter *solo* proporciona una interfaz de cuaderno,\n",
+ "- Scikit-Learn *solo* hace aprendizaje automático,\n",
+ "- h5py *solo* interactúa con archivos HDF5,\n",
+ "- etc.\n",
+ "\n",
+ "Los paquetes de Python para física de altas energías se están desarrollando con un modelo similar:\n",
+ "\n",
+ "- Uproot *solo* lee y escribe archivos ROOT,\n",
+ "- Awkward Array *solo* maneja arrays de tipos irregulares,\n",
+ "- hist *solo* maneja histogramas,\n",
+ "- iminuit *solo* optimiza,\n",
+ "- zfit *solo* ajusta,\n",
+ "- Particle *solo* proporciona datos al estilo PDG,\n",
+ "- etc.\n",
+ "\n",
+ "Para facilitar su localización, están catalogados bajo un nombre común en [scikit-hep.org](https://scikit-hep.org).\n",
+ "\n",
+ "![scikit-hep-logos](fig/scikit-hep-logos.png)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "61",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/scikit_hep_tutorial/02-uproot.ipynb b/scikit_hep_tutorial/02-uproot.ipynb
new file mode 100644
index 0000000..5055993
--- /dev/null
+++ b/scikit_hep_tutorial/02-uproot.ipynb
@@ -0,0 +1,524 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": [
+ "# Operaciones básicas de entrada/salida de archivos con Uproot"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## ¿Qué es Uproot?\n",
+ "\n",
+ "Uproot es un paquete de Python que lee y escribe archivos ROOT, y está *únicamente* enfocado en la lectura y escritura (sin análisis, sin gráficos, etc.). Interactúa con NumPy, Awkward Array y Pandas para cálculos, boost-histogram/hist para manipulación y visualización de histogramas, Vector para funciones y transformaciones de vectores de Lorentz, Coffea para escalado, etc.\n",
+ "\n",
+ "Uproot está implementado solo con Python y librerías de Python. No tiene una parte compilada ni requiere una versión específica de ROOT. (Esto significa que si *usas* ROOT para algo más que entrada/salida, tu elección de versión de ROOT no estará limitada por la entrada/salida).\n",
+ "\n",
+ "![abstraction-layers](fig/abstraction-layers.png)\n",
+ "\n",
+ "Como consecuencia de ser una implementación independiente de entrata/salida de ROOT, Uproot podría no ser capaz de leer/escribir ciertos tipos de datos. Qué tipos de datos no están implementados es un objetivo en constante movimiento, ya que siempre se están agregando nuevos. Una buena forma de leer datos es simplemente intentarlo y ver si Uproot genera algún error. Para la escritura, consulta las listas de tipos compatibles en la [documentación de Uproot](https://uproot.readthedocs.io/en/latest/basic.html#writing-objects-to-a-file) (cajas azules en el texto)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2",
+ "metadata": {},
+ "source": [
+ "## Leer datos desde un archivo\n",
+ "\n",
+ "### Abrir el archivo\n",
+ "\n",
+ "Para abrir un archivo para lectura, pasa el nombre del archivo a [uproot.open](https://uproot.readthedocs.io/en/latest/uproot.reading.open.html). En los scripts, es una buena práctica usar la [instrucción with de Python](https://realpython.com/python-with-statement/) para cerrar el archivo cuando termines, pero si estás trabajando de forma interactiva, puedes usar una asignación directa."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import skhep_testdata\n",
+ "\n",
+ "nombre_del_archivo = skhep_testdata.data_path(\n",
+ " \"uproot-Event.root\"\n",
+ ") # descarga este archivo de prueba y obtiene una ruta local hacia él\n",
+ "\n",
+ "import uproot\n",
+ "\n",
+ "archivo = uproot.open(nombre_del_archivo)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4",
+ "metadata": {},
+ "source": [
+ "Para acceder a un archivo remoto mediante HTTP o XRootD, utiliza una URL que comience con `\"http://...\"`, `\"https://...\"`, o `\"root://...\"`. Si la interfaz de Python para XRootD no está instalada, el mensaje de error explicará cómo instalarla."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5",
+ "metadata": {},
+ "source": [
+ "### Listar contenidos\n",
+ "\n",
+ "Este objeto \"`archivo`\" en realidad representa un directorio, y los objetos nombrados en ese directorio son accesibles a través de una interfaz similar a un diccionario. Por lo tanto, `keys`, `values`, y `items` devuelven los nombres de las claves y/o leen los datos. Si solo quieres listar los objetos sin leerlos, utiliza `keys`. (Esto es similar a `ls()` de ROOT, excepto que obtienes una lista en Python)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "archivo.keys()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7",
+ "metadata": {},
+ "source": [
+ "A menudo, también querrás conocer el tipo de cada objeto, por lo que los objetos [uproot.ReadOnlyDirectory](https://uproot.readthedocs.io/en/latest/uproot.reading.ReadOnlyDirectory.html) también tienen un método `classnames`, que devuelve un diccionario de nombres de objetos a nombres de clases (sin leerlos)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "archivo.classnames()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9",
+ "metadata": {},
+ "source": [
+ "### Lectura de un histograma\n",
+ "\n",
+ "Si estás familiarizado con ROOT, `TH1F` te resultará reconocible como histogramas y `TTree` como un conjunto de datos. Para leer uno de los histogramas, coloca su nombre entre corchetes:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "10",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "h = archivo[\"hstat\"]\n",
+ "h"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "11",
+ "metadata": {},
+ "source": [
+ "Uproot no realiza ningún tipo de graficación ni manipulación de histogramas, por lo que los métodos más útiles de `h` comienzan con \"to\": `to_boost` (boost-histogram), `to_hist` (hist), `to_numpy` (la tupla de 2 elementos de NumPy que contiene el contenido y los bordes), `to_pyroot` (PyROOT), etc."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "12",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "h.to_hist().plot()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "13",
+ "metadata": {},
+ "source": [
+ "Los histogramas de Uproot también cumplen con el [protocolo de graficación UHI](https://uhi.readthedocs.io/en/latest/plotting.html), por lo que tienen métodos como `values` (contenidos de los bins), `variances` (errores al cuadrado) y `axes`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "14",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "h.values()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "15",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "h.variances()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "16",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "list(h.axes[0]) # \"x\", \"y\", \"z\" o 0, 1, 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17",
+ "metadata": {},
+ "source": [
+ "### Lectura de un TTree\n",
+ "\n",
+ "Un TTree representa un conjunto de datos potencialmente grande. Obtenerlo del [uproot.ReadOnlyDirectory](https://uproot.readthedocs.io/en/latest/uproot.reading.ReadOnlyDirectory.html) solo devuelve los nombres y tipos de sus TBranch. El método `show` es una forma conveniente de listar su contenido:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "18",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "t = archivo[\"T\"]\n",
+ "t.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "19",
+ "metadata": {},
+ "source": [
+ "Ten en cuenta que puedes obtener la misma información de `keys` (un [uproot.TTree](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TTree.TTree.html) es similar a un diccionario), `typename` e `interpretation`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "20",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "t.keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "21",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "t[\"event/fNtrack\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "22",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "t[\"event/fNtrack\"].typename"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "23",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "t[\"event/fNtrack\"].interpretation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "24",
+ "metadata": {},
+ "source": [
+ "(Si un [uproot.TBranch](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TBranch.TBranch.html) no tiene `interpretation`, no se puede leer con Uproot.)\n",
+ "\n",
+ "La forma más directa de leer datos de un [uproot.TBranch](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TBranch.TBranch.html) es llamando a su método `array`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "25",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "t[\"event/fNtrack\"].array()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "26",
+ "metadata": {},
+ "source": [
+ "Consideraremos otros métodos en la próxima lección."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "27",
+ "metadata": {},
+ "source": [
+ "### Leyendo un... ¿qué es eso?\n",
+ "\n",
+ "Este archivo también contiene una instancia del tipo [TProcessID](https://root.cern.ch/doc/master/classTProcessID.html). Estos tipos de objetos no son típicamente útiles en el análisis de datos, pero Uproot logra leerlo de todos modos porque sigue ciertas convenciones (tiene \"streamers de clase\"). Se presenta como un objeto genérico con una propiedad `all_members` para sus miembros de datos (a través de todas las superclases)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "28",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "archivo[\"ProcessID0\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "29",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "archivo[\"ProcessID0\"].all_members"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "30",
+ "metadata": {},
+ "source": [
+ "Aquí hay un ejemplo más útil de eso: una búsqueda de supernovas con el experimento IceCube tiene clases personalizadas para sus datos, que Uproot lee y representa como objetos con `all_members`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "31",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "icecube = uproot.open(skhep_testdata.data_path(\"uproot-issue283.root\"))\n",
+ "icecube.classnames()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "32",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "icecube[\"config/detector\"].all_members"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "33",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "icecube[\"config/detector\"].all_members[\"ChannelIDMap\"]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "34",
+ "metadata": {},
+ "source": [
+ "## Escribiendo datos en un archivo\n",
+ "\n",
+ "La capacidad de Uproot para *escribir* datos es más limitada que su capacidad para *leer* datos, pero algunos casos útiles son posibles.\n",
+ "\n",
+ "### Abrir archivos para escribir\n",
+ "\n",
+ "Primero que nada, un archivo debe ser abierto para escribir, ya sea creando un archivo completamente nuevo o actualizando uno existente."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "35",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "archivo_nuevo = uproot.recreate(\"archivo-completamente-nuevo.root\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "36",
+ "metadata": {},
+ "source": [
+ "```python\n",
+ "archivo_existente = uproot.update(\"archivo-existente.root\")\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "37",
+ "metadata": {},
+ "source": [
+ "(Uproot no puede escribir a través de una red; los archivos de salida deben ser locales.)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "38",
+ "metadata": {},
+ "source": [
+ "### Escribiendo cadenas y histogramas\n",
+ "\n",
+ "Estos objetos [uproot.WritableDirectory](https://uproot.readthedocs.io/en/latest/uproot.writing.writable.WritableDirectory.html) tienen una interfaz similar a un diccionario: puedes poner datos en ellos asignando a corchetes.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "39",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "archivo_nuevo[\"una_cadena\"] = \"Este objeto va a ser un TObjString.\"\n",
+ "\n",
+ "archivo_nuevo[\"un_histograma\"] = archivo[\"hstat\"]\n",
+ "\n",
+ "import numpy as np\n",
+ "\n",
+ "archivo_nuevo[\"un_directorio/otro_histograma\"] = np.histogram(\n",
+ " np.random.normal(0, 1, 1000000)\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "40",
+ "metadata": {},
+ "source": [
+ "En ROOT, el nombre de un objeto es una propiedad del objeto, pero en Uproot, es una clave en el TDirectory que contiene el objeto, por lo que el nombre está en el lado izquierdo de la asignación, entre corchetes. Solo se admiten los tipos de datos enumerados en el cuadro azul [en la documentación](https://uproot.readthedocs.io/en/latest/basic.html#writing-objects-to-a-file): principalmente solo histogramas."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "41",
+ "metadata": {},
+ "source": [
+ "### Escribiendo TTrees\n",
+ "\n",
+ "Los TTrees son potencialmente grandes y pueden no caber en la memoria. Generalmente, necesitarás escribirlos en lotes.\n",
+ "\n",
+ "Una forma de hacer esto es asignar el primer lote y `extend` con lotes posteriores:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "42",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "archivo_nuevo[\"tree1\"] = {\n",
+ " \"x\": np.random.randint(0, 10, 1000000),\n",
+ " \"y\": np.random.normal(0, 1, 1000000),\n",
+ "}\n",
+ "archivo_nuevo[\"tree1\"].extend(\n",
+ " {\"x\": np.random.randint(0, 10, 1000000), \"y\": np.random.normal(0, 1, 1000000)}\n",
+ ")\n",
+ "archivo_nuevo[\"tree1\"].extend(\n",
+ " {\"x\": np.random.randint(0, 10, 1000000), \"y\": np.random.normal(0, 1, 1000000)}\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "43",
+ "metadata": {},
+ "source": [
+ "Otra forma es crear un TTree vacío con [uproot.WritableDirectory.mktree](https://uproot.readthedocs.io/en/latest/uproot.writing.writable.WritableDirectory.html#mktree), de modo que cada escritura sea una extensión."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "44",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "archivo_nuevo.mktree(\"tree2\", {\"x\": np.int32, \"y\": np.float64})\n",
+ "archivo_nuevo[\"tree2\"].extend(\n",
+ " {\"x\": np.random.randint(0, 10, 1000000), \"y\": np.random.normal(0, 1, 1000000)}\n",
+ ")\n",
+ "archivo_nuevo[\"tree2\"].extend(\n",
+ " {\"x\": np.random.randint(0, 10, 1000000), \"y\": np.random.normal(0, 1, 1000000)}\n",
+ ")\n",
+ "archivo_nuevo[\"tree2\"].extend(\n",
+ " {\"x\": np.random.randint(0, 10, 1000000), \"y\": np.random.normal(0, 1, 1000000)}\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "45",
+ "metadata": {},
+ "source": [
+ "Se dan consejos de rendimiento en la próxima lección, pero en general, es más beneficioso escribir pocos lotes grandes en lugar de muchos lotes pequeños.\n",
+ "\n",
+ "Los únicos tipos de datos que se pueden asignar o pasar a `extend` están listados en la caja azul [en esta documentación](https://uproot.readthedocs.io/en/latest/basic.html#writing-ttrees-to-a-file). Esto incluye arrays dentados (descritos en la lección después de la próxima), pero no tipos más complejos."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "46",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/scikit_hep_tutorial/03-ttree.ipynb b/scikit_hep_tutorial/03-ttree.ipynb
new file mode 100644
index 0000000..df5b417
--- /dev/null
+++ b/scikit_hep_tutorial/03-ttree.ipynb
@@ -0,0 +1,393 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": [
+ "# Detalles de TTree\n",
+ "\n",
+ "## Estructura y terminología de archivos ROOT\n",
+ "\n",
+ "Un archivo ROOT ([ROOT TFile](https://root.cern.ch/doc/master/classTFile.html), [uproot.ReadOnlyFile](https://uproot.readthedocs.io/en/latest/uproot.reading.ReadOnlyFile.html)) es como un pequeño sistema de archivos que contiene directorios anidados ([ROOT TDirectory](https://root.cern.ch/doc/master/classTDirectory.html), [uproot.ReadOnlyDirectory](https://uproot.readthedocs.io/en/latest/uproot.reading.ReadOnlyDirectory.html)). En Uproot, los directorios anidados se presentan como diccionarios anidados.\n",
+ "\n",
+ "Cualquier instancia de clase ([ROOT TObject](https://root.cern.ch/doc/master/classTObject.html), [uproot.Model](https://uproot.readthedocs.io/en/latest/uproot.model.Model.html)) puede almacenarse en un directorio, incluidos tipos como histogramas (por ejemplo, [ROOT TH1](https://root.cern.ch/doc/master/classTH1.html), [uproot.behaviors.TH1.TH1](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TH1.TH1.html)).\n",
+ "\n",
+ "Una de estas clases, TTree ([ROOT TTree](https://root.cern.ch/doc/master/classTTree.html), [uproot.TTree](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TTree.TTree.html)), es una puerta de entrada a conjuntos de datos grandes. Un TTree es algo parecido a un DataFrame de Pandas en el sentido de que representa una tabla de datos. Las columnas se llaman TBranches ([ROOT TBranch](https://root.cern.ch/doc/master/classTBranch.html), [uproot.TBranch](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TBranch.TBranch.html)), que pueden ser anidadas (a diferencia de Pandas), y los datos pueden tener cualquier tipo de C++ (a diferencia de Pandas, que puede almacenar cualquier tipo de Python).\n",
+ "\n",
+ "Un TTree a menudo es demasiado grande para caber en la memoria, y a veces (raramente) incluso una sola TBranch es demasiado grande para caber en la memoria. Cada TBranch se divide en TBaskets ([ROOT TBasket](https://root.cern/doc/master/classTBasket.html), [uproot.models.TBasket.Model_TBasket](https://uproot.readthedocs.io/en/latest/uproot.models.TBasket.Model_TBasket.html)), que son \"lotes\" de datos. (Estos son los mismos lotes que cada llamada a `extend` escribe en la lección anterior.) Los TBaskets son la unidad más pequeña que se puede leer de un TTree: si deseas leer la primera entrada, debes leer el primer TBasket.\n",
+ "\n",
+ "![terminology](fig/terminology.png)\n",
+ "\n",
+ "Como analista de datos, probablemente te ocuparás de los TTrees y TBranches de manera directa, pero solo de los TBaskets cuando surjan problemas de eficiencia. Los archivos con TBaskets grandes pueden requerir mucha memoria para leer; los archivos con TBaskets pequeños serán más lentos de leer (en ROOT también, pero especialmente en Uproot). Los TBaskets del tamaño de megabytes suelen ser ideales."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {},
+ "source": [
+ "## Ejemplos con un TTree grande\n",
+ "\n",
+ "[Este archivo](http://opendata.web.cern.ch/record/12341) tiene 2.1 GB y está alojado en el Portal de Datos Abiertos de CERN."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import uproot\n",
+ "\n",
+ "archivo = uproot.open(\n",
+ " \"root://eospublic.cern.ch//eos/opendata/cms/derived-data/AOD2NanoAODOutreachTool/Run2012BC_DoubleMuParked_Muons.root\"\n",
+ ")\n",
+ "archivo.classnames()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "```{admonition} ¿Por qué el ;74 y ;75?\n",
+ "Tal vez te hayas preguntado sobre los números después de los puntos y comas. Estos son los \"números de ciclo\" de ROOT, que permiten distinguir objetos con el mismo nombre. Se utilizan cuando un objeto necesita sobrescribirse a medida que crece sin perder la última copia válida de ese objeto, de modo que un archivo ROOT pueda leerse incluso si el proceso de escritura falló a mitad de camino.\n",
+ "\n",
+ "En este caso, la última versión de este TTree es el número 75, y el número 74 es la penúltima.\n",
+ "\n",
+ "Si no especificas números de ciclo, Uproot seleccionará el último por ti, lo cual es casi siempre lo que deseas. (En otras palabras, puedes ignorarlos.)\n",
+ "```\n",
+ "\n",
+ "Simplemente solicitando el objeto [uproot.TTree](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TTree.TTree.html) e imprimiendolo *no* lee todo el conjunto de datos."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tree = archivo[\"Events\"]\n",
+ "tree.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5",
+ "metadata": {},
+ "source": [
+ "### Leyendo una parte de un TTree\n",
+ "\n",
+ "En la lección anterior, aprendimos que la forma más directa de leer una TBranch es llamando a [uproot.TBranch.array](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TBranch.TBranch.html#array)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tree[\"nMuon\"].array()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7",
+ "metadata": {},
+ "source": [
+ "Sin embargo, toma mucho tiempo porque se tiene que enviar una gran cantidad de datos a través de la red.\n",
+ "\n",
+ "Para limitar la cantidad de datos leídos, establece `entry_start` y `entry_stop` en el rango que desees. El `entry_start` es inclusivo, `entry_stop` es exclusivo, y la primera entrada se indexaría por `0`, al igual que los cortes en una interfaz de arreglo (primera lección). Uproot solo lee la cantidad de TBaskets necesarias para proporcionar estas entradas."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tree[\"nMuon\"].array(entry_start=1_000, entry_stop=2_000)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9",
+ "metadata": {},
+ "source": [
+ "Estos son los bloques de construcción de un lector de datos en paralelo: cada uno es responsable de un fragmento diferente. (Consulta también [uproot.TTree.num_entries_for](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TTree.TTree.html#num-entries-for) y [uproot.TTree.common_entry_offsets](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TTree.TTree.html#common-entry-offsets), que se pueden usar para elegir `entry_start`/`entry_stop` de manera óptima.)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "10",
+ "metadata": {},
+ "source": [
+ "### Leer múltiples TBranches a la vez\n",
+ "\n",
+ "Supongamos que sabes que necesitarás todos los TBranches de muones. Pedirlos en una sola solicitud es más eficiente que solicitarlos individualmente, porque el servidor puede estar trabajando en la lectura de los TBaskets posteriores del disco mientras los TBaskets anteriores se envían a ti a través de la red. Mientras que un TBranch tiene un método `array`, el TTree tiene un método `arrays` (en plural) para obtener múltiples arreglos."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "11",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "muones = tree.arrays(\n",
+ " [\"Muon_pt\", \"Muon_eta\", \"Muon_phi\", \"Muon_mass\", \"Muon_charge\"], entry_stop=1_000\n",
+ ")\n",
+ "muones"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12",
+ "metadata": {},
+ "source": [
+ "Ahora, los cinco TBranches están en la salida, `muones`, que es un Awkward Array. Un Awkward Array de múltiples TBranches tiene una interfaz similar a un diccionario, por lo que podemos obtener cada variable de él por"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "13",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "muones[\"Muon_pt\"]\n",
+ "muones[\"Muon_eta\"]\n",
+ "muones[\"Muon_phi\"] # etc."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "14",
+ "metadata": {},
+ "source": [
+ "````{admonition} ¡Cuidado! ¡Es tree.arrays lo que realmente lee los datos!\n",
+ "Si no tienes cuidado con la llamada a [uproot.TTree.arrays](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TTree.TTree.html#arrays), podrías terminar esperando mucho tiempo por datos que no necesitas o podrías quedarte sin memoria. Leer todo con\n",
+ "\n",
+ "```python\n",
+ "todo = tree.arrays()\n",
+ "```\n",
+ "y luego seleccionar los arrays que deseas generalmente no es una buena idea. Al menos, establece un `entry_stop`.\n",
+ "````"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "15",
+ "metadata": {},
+ "source": [
+ "### Selección de TBranches por nombre\n",
+ "\n",
+ "Supongamos que tienes muchos TBranches de muones y no quieres enumerarlos todos. Tanto [uproot.TTree.keys](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TTree.TTree.html#keys) como [uproot.TTree.arrays](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TTree.TTree.html#arrays) aceptan un argumento `filter_name` que puede seleccionarlos de varias maneras (consulta la documentación). En particular, es recomendable usar primero `keys` para saber qué ramas coinciden con tu filtro, seguido de `arrays` para leerlas realmente."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "16",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tree.keys(filter_name=\"Muon_*\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "17",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tree.arrays(filter_name=\"Muon_*\", entry_stop=1_000)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "18",
+ "metadata": {},
+ "source": [
+ "(También hay `filter_typename` y `filter_branch` para más opciones.)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "19",
+ "metadata": {},
+ "source": [
+ "## Escalando, haciendo un gráfico\n",
+ "\n",
+ "La mejor manera de entender lo que estás haciendo es experimentar con conjuntos de datos pequeños y luego escalarlos. Aquí, tomamos 1000 eventos y calculamos las masas de dimuones."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "20",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "muones = tree.arrays(entry_stop=1_000)\n",
+ "corte = muones[\"nMuon\"] == 2\n",
+ "\n",
+ "pt0 = muones[\"Muon_pt\", corte, 0]\n",
+ "pt1 = muones[\"Muon_pt\", corte, 1]\n",
+ "eta0 = muones[\"Muon_eta\", corte, 0]\n",
+ "eta1 = muones[\"Muon_eta\", corte, 1]\n",
+ "phi0 = muones[\"Muon_phi\", corte, 0]\n",
+ "phi1 = muones[\"Muon_phi\", corte, 1]\n",
+ "\n",
+ "import numpy as np\n",
+ "\n",
+ "masa = np.sqrt(2 * pt0 * pt1 * (np.cosh(eta0 - eta1) - np.cos(phi0 - phi1)))\n",
+ "\n",
+ "import hist\n",
+ "\n",
+ "histmasa = hist.Hist(hist.axis.Regular(120, 0, 120, label=\"masa [GeV]\"))\n",
+ "histmasa.fill(masa)\n",
+ "histmasa.plot()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "21",
+ "metadata": {},
+ "source": [
+ "Eso funcionó (hay un pico en Z). Ahora, para hacer esto en todo el archivo, debemos tener más cuidado con lo que estamos leyendo."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "22",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tree.keys(filter_name=[\"nMuon\", \"/Muon_(pt|eta|phi)/\"])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "23",
+ "metadata": {},
+ "source": [
+ "y acumular datos gradualmente con [uproot.TTree.iterate](https://uproot.readthedocs.io/en/latest/uproot.behaviors.TTree.TTree.html#iterate). Esto maneja `entry_start`/`entry_stop` en un bucle."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "24",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "histmasa = hist.Hist(hist.axis.Regular(120, 0, 120, label=\"masa [GeV]\"))\n",
+ "\n",
+ "for muones in tree.iterate(filter_name=[\"nMuon\", \"/Muon_(pt|eta|phi)/\"]):\n",
+ " cut = muones[\"nMuon\"] == 2\n",
+ " pt0 = muones[\"Muon_pt\", cut, 0]\n",
+ " pt1 = muones[\"Muon_pt\", cut, 1]\n",
+ " eta0 = muones[\"Muon_eta\", cut, 0]\n",
+ " eta1 = muones[\"Muon_eta\", cut, 1]\n",
+ " phi0 = muones[\"Muon_phi\", cut, 0]\n",
+ " phi1 = muones[\"Muon_phi\", cut, 1]\n",
+ " masa = np.sqrt(2 * pt0 * pt1 * (np.cosh(eta0 - eta1) - np.cos(phi0 - phi1)))\n",
+ " histmasa.fill(masa)\n",
+ " print(histmasa.sum() / tree.num_entries)\n",
+ "\n",
+ "histmasa.plot()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25",
+ "metadata": {},
+ "source": [
+ "### Obtener datos en NumPy o Pandas\n",
+ "\n",
+ "En todos los ejemplos anteriores, los métodos `array`, `arrays` e `iterate` devuelven arreglos Awkward. La biblioteca Awkward es útil para este tipo de datos (arreglos irregulares: más en la próxima lección), pero es posible que estés trabajando con bibliotecas que solo reconocen arreglos de NumPy o DataFrames de Pandas.\n",
+ "\n",
+ "Utiliza `library=\"np\"` o `library=\"pd\"` para obtener NumPy o Pandas, respectivamente."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "26",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tree[\"nMuon\"].array(library=\"np\", entry_stop=10_000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "27",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tree.arrays(library=\"np\", entry_stop=10_000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "28",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tree.arrays(library=\"pd\", entry_stop=10_000)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "29",
+ "metadata": {},
+ "source": [
+ "NumPy es excelente para datos no irregulares como la rama `\"nMuon\"`, pero tiene que representar un número desconocido de muones por evento como un arreglo de arreglos de NumPy (es decir, objetos de Python).\n",
+ "\n",
+ "Pandas se puede hacer representar múltiples partículas por evento colocando esta estructura en un [pd.MultiIndex](https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html), pero no cuando el DataFrame contiene más de un tipo de partícula (por ejemplo, muones *y* electrones). Usa DataFrames separados para estos casos. Si ayuda, ten en cuenta que hay otra ruta a DataFrames: leyendo los datos como un Arreglo Awkward y llamando a [ak.to_pandas](https://awkward-array.readthedocs.io/en/latest/_auto/ak.to_pandas.html) sobre él. (Algunos métodos usan más memoria que otros, y he encontrado que Pandas es inusualmente intensivo en memoria.)\n",
+ "\n",
+ "O usa Arreglos Awkward (próxima lección)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "30",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/scikit_hep_tutorial/04-awkward.ipynb b/scikit_hep_tutorial/04-awkward.ipynb
new file mode 100644
index 0000000..29ff476
--- /dev/null
+++ b/scikit_hep_tutorial/04-awkward.ipynb
@@ -0,0 +1,767 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": [
+ "# Arreglos irregulares, desiguales y Awkward Array\n",
+ "\n",
+ "## ¿Qué es Awkward Array?\n",
+ "\n",
+ "La lección anterior incluía un corte complicado:\n",
+ "\n",
+ "```python\n",
+ "corte = muones[\"nMuon\"] == 2\n",
+ "\n",
+ "pt0 = muones[\"Muon_pt\", corte, 0]\n",
+ "```\n",
+ "\n",
+ "Las tres partes de `muones[\"Muon_pt\", corte, 0]` son:\n",
+ "\n",
+ "1. selecciona el campo `\"Muon_pt\"` de todos los registros en la matriz,\n",
+ "2. aplica `corte`, una matriz booleana, para seleccionar solo los eventos con dos muones,\n",
+ "3. selecciona el primer (`0`) muón de cada uno de esos pares. De manera similar, para el segundo (`1`) muón.\n",
+ "\n",
+ "NumPy no podría realizar un corte así, ni siquiera representar una matriz de listas de longitud variable sin recurrir a arreglos de objetos."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "np.array([[0.0, 1.1, 2.2], [], [3.3, 4.4], [5.5], [6.6, 7.7, 8.8, 9.9]])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2",
+ "metadata": {},
+ "source": [
+ "Awkward Array está diseñado para llenar este vacío:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import awkward as ak\n",
+ "\n",
+ "ak.Array([[0.0, 1.1, 2.2], [], [3.3, 4.4], [5.5], [6.6, 7.7, 8.8, 9.9]])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4",
+ "metadata": {},
+ "source": [
+ "Arreglos como este se llaman \"irregulares\" o \"desiguales\" (en inglés, [\"jagged arrays\"](https://en.wikipedia.org/wiki/Jagged_array) o a veces \"ragged arrays\")."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5",
+ "metadata": {},
+ "source": [
+ "### Rebanadas en Awkward Array\n",
+ "\n",
+ "Las rebanadas básicas son una generalización de los de NumPy. Lo que NumPy haría si tuviera listas de longitud variable."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "array = ak.Array([[0.0, 1.1, 2.2], [], [3.3, 4.4], [5.5], [6.6, 7.7, 8.8, 9.9]])\n",
+ "array.tolist()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "array[2]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "array[-1, 1]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "array[2:, 0]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "10",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "array[2:, 1:]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "11",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "array[:, 0]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12",
+ "metadata": {},
+ "source": [
+ "**Quiz rápido:** ¿por qué el último genera un error?\n",
+ "\n",
+ "Las rebanadas con booleanos y enteros también funcionan."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "13",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "array[[True, False, True, False, True]]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "14",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "array[[2, 3, 3, 1]]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "15",
+ "metadata": {},
+ "source": [
+ "Como en NumPy, se pueden calcular arreglos booleanos para rebanadas, y funciones como [ak.num](https://awkward-array.readthedocs.io/en/latest/_auto/ak.num.html) son útiles para eso."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "16",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ak.num(array)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "17",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ak.num(array) > 0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "18",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "array[ak.num(array) > 0, 0]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "19",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "array[ak.num(array) > 1, 1]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "20",
+ "metadata": {},
+ "source": [
+ "Ahora considera esto (similar a un ejemplo de la primera lección):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "21",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "corte = array * 10 % 2 == 0\n",
+ "\n",
+ "array[corte]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "22",
+ "metadata": {},
+ "source": [
+ "Este arreglo, `corte`, no es solo un arreglo de booleanos. Es un arreglo irrecular de booleanos. Todas sus listas anidadas encajan en las listas anidadas de `array`, por lo que puede seleccionar profundamente números, en lugar de seleccionar listas."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "23",
+ "metadata": {},
+ "source": [
+ "### Aplicación: seleccionando partículas, en lugar de eventos\n",
+ "\n",
+ "Volviendo al TTree grande de la lección anterior,"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "24",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import uproot\n",
+ "\n",
+ "archivo = uproot.open(\n",
+ " \"root://eospublic.cern.ch//eos/opendata/cms/derived-data/AOD2NanoAODOutreachTool/Run2012BC_DoubleMuParked_Muons.root\"\n",
+ ")\n",
+ "tree = archivo[\"Events\"]\n",
+ "\n",
+ "muon_pt = tree[\"Muon_pt\"].array(entry_stop=10)\n",
+ "muon_pt"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25",
+ "metadata": {},
+ "source": [
+ "Este arreglo irregular de booleanos selecciona todos los *muones* con al menos 20 GeV:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "26",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "corte_particula = muon_pt > 20\n",
+ "\n",
+ "muon_pt[corte_particula]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "27",
+ "metadata": {},
+ "source": [
+ "y este arreglo de booleanos no irregular (hecho con [ak.any](https://awkward-array.readthedocs.io/en/latest/_auto/ak.any.html)) selecciona todos los eventos *que tienen* un muón con al menos 20 GeV:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "28",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "corte_evento = ak.any(muon_pt > 20, axis=1)\n",
+ "\n",
+ "muon_pt[corte_evento]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "29",
+ "metadata": {},
+ "source": [
+ "**Quiz rápido:** construye exactamente el mismo `corte_evento` utilizando [ak.max](https://awkward-array.readthedocs.io/en/latest/_auto/ak.max.html).\n",
+ "\n",
+ "**Quiz rápido:** aplica ambos cortes; es decir, selecciona muones con más de 20 GeV de los eventos que los tienen.\n",
+ "\n",
+ "Sugerencia: querrás hacer un\n",
+ "\n",
+ "```python\n",
+ "muones_seleccionados = muon_pt[corte_particula]\n",
+ "```\n",
+ "intermediario y no puedes usar la variable `corte_evento` tal como está.\n",
+ "\n",
+ "Sugerencia: el resultado final debería ser un arreglo irregular, al igual que `muon_pt`, pero con menos listas y menos elementos en esas listas."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "30",
+ "metadata": {},
+ "source": [
+ "````{admonition} Solución\n",
+ ":class: dropdown\n",
+ "```python\n",
+ "muones_seleccionados = muon_pt[corte_particula]\n",
+ "resultado_final = muones_seleccionados[corte_evento]\n",
+ "resultado_final.tolist()\n",
+ "```\n",
+ "````"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "31",
+ "metadata": {},
+ "source": [
+ "## Combinatoria en Awkward Array\n",
+ "\n",
+ "Las listas de longitud variable presentan más problemas que solo el corte y el cálculo de fórmulas en arrays. A menudo, queremos combinar partículas en todos los pares posibles (dentro de cada evento) para buscar cadenas de descomposición.\n",
+ "\n",
+ "### Pares de dos arrays, pares de un solo array\n",
+ "\n",
+ "Awkward Array tiene funciones que generan estas combinaciones. Por ejemplo, [ak.cartesian](https://awkward-array.readthedocs.io/en/latest/_auto/ak.cartesian.html) toma un producto cartesiano por evento (cuando `axis=1`, el valor predeterminado).\n",
+ "\n",
+ "![cartoon-cartesian](fig/cartoon-cartesian.png)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "32",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "numeros = ak.Array([[1, 2, 3], [], [5, 7], [11]])\n",
+ "letras = ak.Array([[\"a\", \"b\"], [\"c\"], [\"d\"], [\"e\", \"f\"]])\n",
+ "\n",
+ "pares = ak.cartesian((numeros, letras))\n",
+ "pares"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "33",
+ "metadata": {},
+ "source": [
+ "Estos `pares` son 2-tuplas, que son como registros en cómo se extraen de un array: usando cadenas."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "34",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pares[\"0\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "35",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pares[\"1\"]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "36",
+ "metadata": {},
+ "source": [
+ "También hay [ak.unzip](https://awkward-array.readthedocs.io/en/latest/_auto/ak.unzip.html), que extrae cada campo en un array separado (lo opuesto de [ak.zip](https://awkward-array.readthedocs.io/en/latest/_auto/ak.zip.html))."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "37",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "izquierda, derecha = ak.unzip(pares)\n",
+ "izquierda"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "38",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "derecha"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "39",
+ "metadata": {},
+ "source": [
+ "Tenga en cuenta que estos `izquierda` y `derecha` no son los `numeros` y `letras` originales: han sido duplicados y tienen la misma forma.\n",
+ "\n",
+ "El producto cartesiano es equivalente a este bucle `for` en C++ sobre dos colecciones:\n",
+ "\n",
+ "```cpp\n",
+ "for (int i = 0; i < numeros.size(); i++) {\n",
+ " for (int j = 0; j < letras.size(); j++) {\n",
+ " // formula con numeros[i] y letras[j]\n",
+ " }\n",
+ "}\n",
+ "```\n",
+ "\n",
+ "A veces, sin embargo, queremos encontrar todos los pares dentro de una sola colección, sin repetición. Eso sería equivalente a este bucle `for` en C++:\n",
+ "\n",
+ "```cpp\n",
+ "for (int i = 0; i < numeros.size(); i++) {\n",
+ " for (int j = i + 1; i < numeros.size(); j++) {\n",
+ " // formula con numeros[i] y numeros[j]\n",
+ " }\n",
+ "}\n",
+ "```\n",
+ "\n",
+ "La función Awkward para este caso es [ak.combinations](https://awkward-array.readthedocs.io/en/latest/_auto/ak.combinations.html).\n",
+ "\n",
+ "![cartoon-combinations](fig/cartoon-combinations.png)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "40",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pares = ak.combinations(numeros, 2)\n",
+ "pares"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "41",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "izquierda, derecha = ak.unzip(pares)\n",
+ "izquierda * derecha # Se alinean, por lo que podemos calcular fórmulas"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "42",
+ "metadata": {},
+ "source": [
+ "### Aplicación a los dimuones\n",
+ "\n",
+ "La búsqueda de dimuones en la lección anterior fue un poco ingenua en el sentido de que requeríamos *exactamente dos* muones en cada evento y solo calculamos la masa de esa combinación. Si hubiera un tercer muón presente debido a una compleja descomposición electrodébil o porque algo fue medido incorrectamente, estaríamos ciegos a los otros dos muones. Podrían ser dimuones reales.\n",
+ "\n",
+ "Un mejor procedimiento sería buscar todos los pares de muones en un evento y aplicar algunos criterios para seleccionarlos.\n",
+ "\n",
+ "En este ejemplo, juntaremos (usando [ak.zip](https://awkward-array.readthedocs.io/en/latest/_auto/ak.zip.html)) las variables de los muones en registros."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "43",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import uproot\n",
+ "import awkward as ak\n",
+ "\n",
+ "archivo = uproot.open(\n",
+ " \"root://eospublic.cern.ch//eos/opendata/cms/derived-data/AOD2NanoAODOutreachTool/Run2012BC_DoubleMuParked_Muons.root\"\n",
+ ")\n",
+ "tree = archivo[\"Events\"]\n",
+ "\n",
+ "arrays = tree.arrays(filter_name=\"/Muon_(pt|eta|phi|charge)/\", entry_stop=10000)\n",
+ "\n",
+ "muones = ak.zip(\n",
+ " {\n",
+ " \"pt\": arrays[\"Muon_pt\"],\n",
+ " \"eta\": arrays[\"Muon_eta\"],\n",
+ " \"phi\": arrays[\"Muon_phi\"],\n",
+ " \"charge\": arrays[\"Muon_charge\"],\n",
+ " }\n",
+ ")\n",
+ "\n",
+ "print(arrays.type)\n",
+ "print(muones.type)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "44",
+ "metadata": {},
+ "source": [
+ "La diferencia entre `arrays` y `muones` es que `arrays` contiene listas separadas de `\"Muon_pt\"`, `\"Muon_eta\"`, `\"Muon_phi\"`, `\"Muon_charge\"`, mientras que `muons` contiene listas de registros con los campos `\"pt\"`, `\"eta\"`, `\"phi\"`, `\"charge\"`.\n",
+ "\n",
+ "Ahora podemos calcular pares de *objetos* muones."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "45",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pares = ak.combinations(muons, 2)\n",
+ "pares.type"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "46",
+ "metadata": {},
+ "source": [
+ "y separarlos en arreglos del primer muón y del segundo muón en cada par."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "47",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mu1, mu2 = ak.unzip(pares)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "48",
+ "metadata": {},
+ "source": [
+ "**Quiz rápido:** ¿cómo garantizarías que todas las listas de registros en `mu1` y `mu2` tengan las mismas longitudes? Sugerencia: consulta [ak.num](https://awkward-array.readthedocs.io/en/latest/_auto/ak.num.html) y [ak.all](https://awkward-array.readthedocs.io/en/latest/_auto/ak.all.html).\n",
+ "\n",
+ "Dado que tienen las mismas longitudes, podemos usarlos en una fórmula."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "49",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "masa = np.sqrt(\n",
+ " 2 * mu1.pt * mu2.pt * (np.cosh(mu1.eta - mu2.eta) - np.cos(mu1.phi - mu2.phi))\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "50",
+ "metadata": {},
+ "source": [
+ "**Quiz rápido:** ¿cuántas masas tenemos en cada evento? ¿Cómo se compara esto con `muons`, `mu1` y `mu2`?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "51",
+ "metadata": {},
+ "source": [
+ "### Graficando el arreglo irregular\n",
+ "\n",
+ "Dado que esta `masa` es un arreglo irregular, no se puede histogramar directamente. Los histogramas toman un conjunto de *números* como entradas, pero este arreglo contiene *listas*.\n",
+ "\n",
+ "Suponiendo que solo deseas graficar los números de las listas, puedes usar [ak.flatten](https://awkward-array.readthedocs.io/en/latest/_auto/ak.flatten.html) para aplanar un nivel de lista o [ak.ravel](https://awkward-array.readthedocs.io/en/latest/_auto/ak.ravel.html) para aplanar todos los niveles."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "52",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import hist\n",
+ "\n",
+ "hist.Hist(hist.axis.Regular(120, 0, 120, label=\"masa [GeV]\")).fill(\n",
+ " ak.ravel(masa)\n",
+ ").plot()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "53",
+ "metadata": {},
+ "source": [
+ "Alternativamente, supongamos que deseas graficar la *máxima* masa candidata en cada evento, sesgándola hacia los bosones Z. [ak.max](https://awkward-array.readthedocs.io/en/latest/_auto/ak.max.html) es una función diferente que selecciona un elemento de cada lista, cuando se utiliza con `axis=1`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "54",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ak.max(masa, axis=1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "55",
+ "metadata": {},
+ "source": [
+ "Algunos valores son `None` porque no hay un máximo en una lista vacía. [ak.flatten](https://awkward-array.readthedocs.io/en/latest/_auto/ak.flatten.html)/[ak.ravel](https://awkward-array.readthedocs.io/en/latest/_auto/ak.ravel.html) eliminan los valores faltantes (`None`) así como aplastan las listas,"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "56",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ak.flatten(ak.max(masa, axis=1), axis=0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "57",
+ "metadata": {},
+ "source": [
+ "pero también lo hace eliminar las listas vacías en primer lugar."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "58",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ak.max(masa[ak.num(masa) > 0], axis=1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "59",
+ "metadata": {},
+ "source": [
+ "`````{admonition} Ejercicio: seleccionar pares de muones con cargas opuestas\n",
+ "Este no es un corte a nivel de evento ni un corte a nivel de partículas, es un corte sobre *pares* de partículas.\n",
+ "\n",
+ "````{toggle} Solución\n",
+ "Las variables `mu1` y `mu2` son las mitades izquierda y derecha de los pares de muones. Por lo tanto,\n",
+ "\n",
+ "```python\n",
+ "corte = (mu1.charge != mu2.charge)\n",
+ "```\n",
+ "tiene la multiplicidad correcta para aplicarse a la matriz `masa`.\n",
+ "\n",
+ "```python\n",
+ "hist.Hist(hist.axis.Regular(120, 0, 120, label=\"masa [GeV]\")).fill(\n",
+ " ak.ravel(mass[cut])\n",
+ ").plot()\n",
+ "```\n",
+ "plotea los pares de muones seleccionados.\n",
+ "````\n",
+ "`````"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "60",
+ "metadata": {},
+ "source": [
+ "`````{admonition} Ejercicio (más difícil): traza el candidato a masa por evento que esté estrictamente más cercano a la masa del Z\n",
+ "En lugar de solo tomar la masa máxima en cada evento, encuentra la que tenga la diferencia mínima entre la masa calculada y `masa_z = 91`.\n",
+ "\n",
+ "Sugerencia: usa [ak.argmin](https://awkward-array.readthedocs.io/en/latest/_auto/ak.argmin.html) con `keepdims=True`.\n",
+ "\n",
+ "Anticipando una de las futuras lecciones, podrías obtener una masa más precisa pidiendo a la librería Particle:\n",
+ "\n",
+ "```python\n",
+ "import particle, hepunits\n",
+ "\n",
+ "masa_z = particle.Particle.findall(\"Z0\")[0].mass / hepunits.GeV\n",
+ "```\n",
+ "\n",
+ "````{toggle} Solución\n",
+ "En lugar de maximizar `masa`, queremos minimizar `abs(masa - masa_z)` y aplicar esa elección a `masa`. [ak.argmin](https://awkward-array.readthedocs.io/en/latest/_auto/ak.argmin.html) devuelve la *posición del índice* de esta diferencia mínima, que luego podemos aplicar a la `masa` original. Sin embargo, sin `keepdims=True`, [ak.argmin](https://awkward-array.readthedocs.io/en/latest/_auto/ak.argmin.html) elimina la dimensión que necesitaríamos para que esta matriz tenga la misma forma anidada que `masa`. Por lo tanto, usamos `keepdims=True` y luego utilizamos [ak.ravel](https://awkward-array.readthedocs.io/en/latest/_auto/ak.ravel.html) para deshacernos de los valores faltantes y aplanar las listas.\n",
+ "\n",
+ "El último paso requeriría dos aplicaciones de [ak.flatten](https://awkward-array.readthedocs.io/en/latest/_auto/ak.flatten.html): una para aplastar listas en el primer nivel y otra para eliminar `None` en el segundo nivel.\n",
+ "\n",
+ "```python\n",
+ "cual = ak.argmin(abs(masa - masa_z), axis=1, keepdims=True)\n",
+ "\n",
+ "hist.Hist(hist.axis.Regular(120, 0, 120, label=\"masa [GeV]\")).fill(\n",
+ " ak.ravel(masa[cual])\n",
+ "\n",
+ ").plot()\n",
+ "```\n",
+ "````\n",
+ "`````"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/scikit_hep_tutorial/05-histogramas.ipynb b/scikit_hep_tutorial/05-histogramas.ipynb
new file mode 100644
index 0000000..3823a0b
--- /dev/null
+++ b/scikit_hep_tutorial/05-histogramas.ipynb
@@ -0,0 +1,384 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": [
+ "# Manipulaciones y ajustes de histogramas\n",
+ "\n",
+ "## Librerías de histogramas\n",
+ "\n",
+ "Python tiene librerías para llenar histogramas.\n",
+ "\n",
+ "### NumPy\n",
+ "\n",
+ "NumPy, por ejemplo, tiene una función [np.histogram](https://numpy.org/doc/stable/reference/generated/numpy.histogram.html)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import skhep_testdata, uproot\n",
+ "\n",
+ "tree = uproot.open(skhep_testdata.data_path(\"uproot-Zmumu.root\"))[\"events\"]\n",
+ "\n",
+ "import numpy as np\n",
+ "\n",
+ "np.histogram(tree[\"M\"].array())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2",
+ "metadata": {},
+ "source": [
+ "Debido a la prominencia de NumPy, este 2-tupla de arreglos (contenidos de los bins y bordes) es un formato de histograma ampliamente reconocido, aunque carece de muchas de las características que los físicos de alta energía esperan (sub/sobre flujo, etiquetas de ejes, incertidumbres, etc.).\n",
+ "\n",
+ "### Matplotlib\n",
+ "\n",
+ "Matplotlib también tiene una función [plt.hist](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.hist.html)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "plt.hist(tree[\"M\"].array())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4",
+ "metadata": {},
+ "source": [
+ "Además de los mismos contenidos de bins y bordes que NumPy, Matplotlib incluye un gráfico que se puede graficar."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5",
+ "metadata": {},
+ "source": [
+ "### Boost-histogram y hist\n",
+ "\n",
+ "La característica principal que falta en estas funciones (sin algún esfuerzo) es la posibilidad de rellenar nuevamente. Los físicos de altas energías generalmente quieren llenar histogramas con más datos de los que pueden caber en memoria, lo que significa establecer intervalos de bins en un contenedor vacío y llenarlo en lotes (secuencialmente o en paralelo).\n",
+ "\n",
+ "Boost-histogram es una biblioteca diseñada para ese propósito. Está destinada a ser un componente de infraestructura. Puedes explorar su funcionalidad \"de bajo nivel\" al importarla:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import boost_histogram as bh"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7",
+ "metadata": {},
+ "source": [
+ "Una capa más amigable para el usuario (con gráficos, por ejemplo) es proporcionada por una biblioteca llamada \"hist.\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import hist\n",
+ "\n",
+ "h = hist.Hist(hist.axis.Regular(120, 60, 120, name=\"masa\"))\n",
+ "\n",
+ "h.fill(tree[\"M\"].array())\n",
+ "\n",
+ "h.plot()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9",
+ "metadata": {},
+ "source": [
+ "### Indexación Universal de Histogramas (UHI)\n",
+ "\n",
+ "Hay un intento dentro de Scikit-HEP para estandarizar lo que significan las rebanadas similares a arreglos para un histograma. ([Ver documentación](https://uhi.readthedocs.io/en/latest/indexing.html).)\n",
+ "\n",
+ "Naturalmente, las rebanadas enteras deberían seleccionar un rango de bins,"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "10",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "h[10:110].plot()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "11",
+ "metadata": {},
+ "source": [
+ "pero a menudo deseamos seleccionar bins por valor de coordenada."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "12",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Versión explícita\n",
+ "h[hist.loc(90) :].plot()\n",
+ "\n",
+ "# Versión corta\n",
+ "h[90j:].plot()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "13",
+ "metadata": {},
+ "source": [
+ "o rebinar por un factor,"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "14",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Versión explícita\n",
+ "h[:: hist.rebin(2)].plot()\n",
+ "\n",
+ "# Versión corta\n",
+ "h[::2j].plot()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "15",
+ "metadata": {},
+ "source": [
+ "o sumar sobre un rango."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "16",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Versión explícita\n",
+ "h[hist.loc(80) : hist.loc(100) : sum]\n",
+ "\n",
+ "# Versión corta\n",
+ "h[90j:100j:sum]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17",
+ "metadata": {},
+ "source": [
+ "Las cosas se vuelven más interesantes cuando un histograma tiene múltiples dimensiones."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "18",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import uproot\n",
+ "import hist\n",
+ "import awkward as ak\n",
+ "\n",
+ "picodst = uproot.open(\n",
+ " \"https://pivarski-princeton.s3.amazonaws.com/pythia_ppZee_run17emb.picoDst.root:PicoDst\"\n",
+ ")\n",
+ "\n",
+ "hist_vertices = hist.Hist(\n",
+ " hist.axis.Regular(600, -1, 1, label=\"x\"),\n",
+ " hist.axis.Regular(600, -1, 1, label=\"y\"),\n",
+ " hist.axis.Regular(40, -200, 200, label=\"z\"),\n",
+ ")\n",
+ "\n",
+ "datos_vertices = picodst.arrays(filter_name=\"*mPrimaryVertex[XYZ]\")\n",
+ "\n",
+ "hist_vertices.fill(\n",
+ " ak.flatten(datos_vertice[\"Event.mPrimaryVertexX\"]),\n",
+ " ak.flatten(datos_vertice[\"Event.mPrimaryVertexY\"]),\n",
+ " ak.flatten(datos_vertice[\"Event.mPrimaryVertexZ\"]),\n",
+ ")\n",
+ "\n",
+ "hist_vertices[:, :, sum].plot2d_full()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "19",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "hist_vertices[-0.25j:0.25j, -0.25j:0.25j, sum].plot2d_full()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "20",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "hist_vertices[sum, sum, :].plot()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "21",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "hist_vertices[-0.25j:0.25j:sum, -0.25j:0.25j:sum, :].plot()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "22",
+ "metadata": {},
+ "source": [
+ "Un objeto de histograma puede tener más dimensiones de las que se pueden visualizar razonablemente; puedes rebanar, rebin y proyectar en algo visual más tarde."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "23",
+ "metadata": {},
+ "source": [
+ "## Ajuste de histogramas\n",
+ "\n",
+ "Escribiendo directamente una función de pérdida en Minuit:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "24",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import iminuit.cost\n",
+ "\n",
+ "norm = len(h.axes[0].widths) / (h.axes[0].edges[-1] - h.axes[0].edges[0]) / h.sum()\n",
+ "\n",
+ "\n",
+ "def f(x, background, mu, gamma):\n",
+ " return (\n",
+ " background\n",
+ " + (1 - background) * gamma**2 / ((x - mu) ** 2 + gamma**2) / np.pi / gamma\n",
+ " )\n",
+ "\n",
+ "\n",
+ "loss = iminuit.cost.LeastSquares(\n",
+ " h.axes[0].centers, h.values() * norm, np.sqrt(h.variances()) * norm, f\n",
+ ")\n",
+ "loss.mask = h.variances() > 0\n",
+ "\n",
+ "minimizer = iminuit.Minuit(loss, background=0, mu=91, gamma=4)\n",
+ "\n",
+ "minimizer.migrad()\n",
+ "minimizer.hesse()\n",
+ "\n",
+ "(h * norm).plot()\n",
+ "plt.plot(loss.x, f(loss.x, *minimizer.values))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25",
+ "metadata": {},
+ "source": [
+ "O a través de zfit, un ajustador similar a RooFit en Python:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "26",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import zfit\n",
+ "\n",
+ "binned_data = zfit.data.BinnedData.from_hist(h)\n",
+ "\n",
+ "binning = zfit.binned.RegularBinning(120, 60, 120, name=\"masa\")\n",
+ "space = zfit.Space(\"masa\", binning=binning)\n",
+ "\n",
+ "background = zfit.Parameter(\"background\", 0)\n",
+ "mu = zfit.Parameter(\"mu\", 91)\n",
+ "gamma = zfit.Parameter(\"gamma\", 4)\n",
+ "unbinned_model = zfit.pdf.SumPDF(\n",
+ " [zfit.pdf.Uniform(60, 120, space), zfit.pdf.Cauchy(mu, gamma, space)], [background]\n",
+ ")\n",
+ "\n",
+ "model = zfit.pdf.BinnedFromUnbinnedPDF(unbinned_model, space)\n",
+ "loss = zfit.loss.BinnedNLL(model, binned_data)\n",
+ "\n",
+ "minimizer = zfit.minimize.Minuit()\n",
+ "result = minimizer.minimize(loss)\n",
+ "\n",
+ "binned_data.to_hist().plot(density=1)\n",
+ "model.to_hist().plot(density=1)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/scikit_hep_tutorial/06-vectores-lorentz.ipynb b/scikit_hep_tutorial/06-vectores-lorentz.ipynb
new file mode 100644
index 0000000..15d732c
--- /dev/null
+++ b/scikit_hep_tutorial/06-vectores-lorentz.ipynb
@@ -0,0 +1,344 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {},
+ "source": [
+ "# Vectores de Lorentz, IDs de partículas PDG, agrupamiento de jets, ¡ay!\n",
+ "\n",
+ "## Vectores de Lorentz\n",
+ "\n",
+ "Siguiendo con la filosofía de \"muchos paquetes pequeños\", los vectores 2D/3D/Lorentz son manejados por un paquete llamado Vector. Aquí es donde puedes encontrar cálculos como `deltaR` y transformaciones de coordenadas."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import vector\n",
+ "\n",
+ "uno = vector.obj(px=1, py=0, pz=0)\n",
+ "dos = vector.obj(px=0, py=1, pz=1)\n",
+ "\n",
+ "uno + dos"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "uno.deltaR(dos)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "uno.to_rhophieta()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "dos.to_rhophieta()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5",
+ "metadata": {},
+ "source": [
+ "Para integrarse con el resto del ecosistema, Vector debe ser una librería orientada a arreglos. Los arreglos de vectores 2D/3D/Lorentz se procesan en masa.\n",
+ "\n",
+ "`MomentumNumpy2D`, `MomentumNumpy3D`, `MomentumNumpy4D` son subtipos de arreglos NumPy: los arreglos de NumPy pueden ser *convertidos* a estos tipos y obtener todas las funciones vectoriales."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import skhep_testdata, uproot\n",
+ "import awkward as ak\n",
+ "import vector\n",
+ "\n",
+ "tree = uproot.open(skhep_testdata.data_path(\"uproot-Zmumu.root\"))[\"events\"]\n",
+ "\n",
+ "uno = ak.to_numpy(tree.arrays(filter_name=[\"E1\", \"p[xyz]1\"]))\n",
+ "dos = ak.to_numpy(tree.arrays(filter_name=[\"E2\", \"p[xyz]2\"]))\n",
+ "\n",
+ "uno.dtype.names = (\"E\", \"px\", \"py\", \"pz\")\n",
+ "dos.dtype.names = (\"E\", \"px\", \"py\", \"pz\")\n",
+ "\n",
+ "uno = uno.view(vector.MomentumNumpy4D)\n",
+ "dos = dos.view(vector.MomentumNumpy4D)\n",
+ "\n",
+ "uno + dos"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "uno.deltaR(dos)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "uno.to_rhophieta()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "dos.to_rhophieta()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "10",
+ "metadata": {},
+ "source": [
+ "Después de llamar a `vector.register_awkward()`, `\"Momentum2D\"`, `\"Momentum3D\"`, `\"Momentum4D\"` son nombres de registros que Awkward Array reconocerá para obtener todas las funciones vectoriales."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "11",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "vector.register_awkward()\n",
+ "\n",
+ "tree = uproot.open(skhep_testdata.data_path(\"uproot-HZZ.root\"))[\"events\"]\n",
+ "\n",
+ "array = tree.arrays(filter_name=[\"Muon_E\", \"Muon_P[xyz]\"])\n",
+ "\n",
+ "muones = ak.zip(\n",
+ " {\"px\": array.Muon_Px, \"py\": array.Muon_Py, \"pz\": array.Muon_Pz, \"E\": array.Muon_E},\n",
+ " with_name=\"Momentum4D\",\n",
+ ")\n",
+ "mu1, mu2 = ak.unzip(ak.combinations(muones, 2))\n",
+ "\n",
+ "mu1 + mu2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "12",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mu1.deltaR(mu2)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "13",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "muones.to_rhophieta()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "14",
+ "metadata": {},
+ "source": [
+ "## Propiedades de partículas e identificadores PDG\n",
+ "\n",
+ "La librería Particle proporciona todas las masas de partículas, anchos de decaimiento y más del PDG.\n",
+ "Además, contiene una serie de herramientas para consultar programáticamente las propiedades de las partículas y usar varios esquemas de identificación."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "15",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import particle\n",
+ "from hepunits import GeV\n",
+ "\n",
+ "particle.Particle.findall(\"pi\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "16",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "boson_z = particle.Particle.from_name(\"Z0\")\n",
+ "boson_z.mass / GeV, boson_z.width / GeV"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "17",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(boson_z.describe())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "18",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "particle.Particle.from_pdgid(111)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "19",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "particle.Particle.findall(\n",
+ " lambda p: p.pdgid.is_meson and p.pdgid.has_strange and p.pdgid.has_charm\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "20",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(particle.PDGID(211).info())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "21",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pdgid = particle.Corsika7ID(11).to_pdgid()\n",
+ "particle.Particle.from_pdgid(pdgid)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "22",
+ "metadata": {},
+ "source": [
+ "# Agrupamiento de jets\n",
+ "\n",
+ "En una colisión pp de alta energía, por ejemplo, se produce una lluvia de hadrones que se agrupan en `jets` de partículas, y este método/proceso se llama agrupamiento de jets (jet-clustering). El algoritmo de agrupamiento de jets anti-kt es uno de los algoritmos utilizados para combinar partículas/hadrones que están cerca entre sí en jets.\n",
+ "\n",
+ "Algunas personas necesitan realizar agrupamiento de jets a nivel de análisis. El paquete fastjet hace posible hacerlo un array (Awkward) a la vez."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "23",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import skhep_testdata, uproot\n",
+ "import awkward as ak\n",
+ "import particle\n",
+ "from hepunits import GeV\n",
+ "import vector\n",
+ "\n",
+ "vector.register_awkward()\n",
+ "\n",
+ "picodst = uproot.open(\n",
+ " \"https://pivarski-princeton.s3.amazonaws.com/pythia_ppZee_run17emb.picoDst.root:PicoDst\"\n",
+ ")\n",
+ "px, py, pz = ak.unzip(\n",
+ " picodst.arrays(filter_name=[\"Track/Track.mPMomentum[XYZ]\"], entry_stop=100)\n",
+ ")\n",
+ "\n",
+ "probable_mass = particle.Particle.from_name(\"pi+\").mass / GeV\n",
+ "\n",
+ "pseudojets = ak.zip(\n",
+ " {\"px\": px, \"py\": py, \"pz\": pz, \"mass\": probable_mass}, with_name=\"Momentum4D\"\n",
+ ")\n",
+ "good_pseudojets = pseudojets[pseudojets.pt > 0.1]\n",
+ "\n",
+ "import fastjet\n",
+ "\n",
+ "jetdef = fastjet.JetDefinition(fastjet.antikt_algorithm, 1.0)\n",
+ "\n",
+ "clusterseq = fastjet.ClusterSequence(good_pseudojets, jetdef)\n",
+ "clusterseq.inclusive_jets()\n",
+ "\n",
+ "ak.num(good_pseudojets), ak.num(clusterseq.inclusive_jets())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "24",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/scikit_hep_tutorial/07-mas-herramientas.md b/scikit_hep_tutorial/07-mas-herramientas.md
new file mode 100644
index 0000000..2f5a3d2
--- /dev/null
+++ b/scikit_hep_tutorial/07-mas-herramientas.md
@@ -0,0 +1,35 @@
+# Herramientas para seguir escalando
+
+Las herramientas descritas en estas lecciones están destinadas a ser utilizadas *dentro* de un script que se escale para conjuntos de datos grandes.
+
+Puedes usar cualquiera de ellas en un trabajo GRID ordinario (u otro procesador por lotes).
+
+Sin embargo, el [proyecto Coffea](https://github.com/CoffeaTeam) ([documentación](https://coffeateam.github.io/coffea/)) está construyendo un ecosistema *distribuido* que integra el análisis en Python con granjas de análisis de datos. Este es un tema demasiado amplio para cubrir aquí, pero consulta el software y únete a las [reuniones de usuarios de Coffea](https://indico.cern.ch/category/11674/) si estás interesado.
+
+# Recursos de Scikit-HEP
+
+- [scikit-hep.org](https://scikit-hep.org)
+- Uproot [GitHub](https://github.com/scikit-hep/uproot5), [documentación](https://uproot.readthedocs.io)
+- Awkward Array [GitHub](https://github.com/scikit-hep/awkward-1.0), [documentación](https://awkward-array.org)
+- boost-histogram [GitHub](https://github.com/scikit-hep/boost-histogram), [documentación](https://boost-histogram.readthedocs.io)
+- hist [GitHub](https://github.com/scikit-hep/hist), [documentación](https://hist.readthedocs.io)
+- Unified Histogram Interface [GitHub](https://github.com/scikit-hep/uhi), [documentación](https://uhi.readthedocs.io)
+- mplhep [GitHub](https://github.com/scikit-hep/mplhep), [documentación](https://mplhep.readthedocs.io)
+- iminuit [GitHub](https://github.com/scikit-hep/iminuit), [documentación](https://iminuit.readthedocs.io)
+- zfit [GitHub](https://github.com/zfit/zfit), [documentación](https://zfit.readthedocs.io)
+- Vector [GitHub](https://github.com/scikit-hep/vector), [documentación](https://vector.readthedocs.io)
+- Particle [GitHub](https://github.com/scikit-hep/particle), [documentación](https://github.com/scikit-hep/particle/blob/master/notebooks/ParticleDemo.ipynb)
+- hepunits [GitHub](https://github.com/scikit-hep/hepunits)
+- fastjet [GitHub](https://github.com/scikit-hep/fastjet), [documentación](https://fastjet.readthedocs.io)
+- pyhf [GitHub](https://github.com/scikit-hep/pyhf), [documentación](https://pyhf.readthedocs.io)
+- hepstats [GitHub](https://github.com/scikit-hep/hepstats), [documentación](https://scikit-hep.org/hepstats)
+- cabinetry [GitHub](https://github.com/scikit-hep/cabinetry), [documentación](https://iris-hep.org/projects/cabinetry.html)
+- histoprint [GitHub](https://github.com/scikit-hep/histoprint)
+- decaylanguage [GitHub](https://github.com/scikit-hep/decaylanguage), [documentación](https://github.com/scikit-hep/decaylanguage/blob/master/notebooks/DecayLanguageDemo.ipynb)
+- GooFit [GitHub](https://github.com/GooFit/GooFit), [documentación](https://goofit.github.io/)
+- pyhepmc [GitHub](https://github.com/scikit-hep/pyhepmc)
+- pylhe [GitHub](https://github.com/scikit-hep/pylhe)
+
+y finalmente
+
+- cookie [GitHub](https://github.com/scientific-python/cookie), [documentación](https://learn.scientific-python.org/development), una plantilla para crear la tuya propia...
\ No newline at end of file
diff --git a/scikit_hep_tutorial/_config.yml b/scikit_hep_tutorial/_config.yml
new file mode 100644
index 0000000..a4ef932
--- /dev/null
+++ b/scikit_hep_tutorial/_config.yml
@@ -0,0 +1,40 @@
+#######################################################################################
+# A default configuration that will be loaded for all jupyter books
+# See the documentation for help and more options:
+# https://jupyterbook.org/customize/config.html
+
+#######################################################################################
+# Book settings
+title: Scikit-HEP Tutorial # The title of the book. Will be placed in the left navbar.
+author: HSF Training # The author of the book
+copyright: "2024" # Copyright year to be placed in the footer
+logo: hsf_logo.png # A path to the book logo
+
+# Force re-execution of notebooks on each build.
+# See https://jupyterbook.org/content/execute.html
+execute:
+ execute_notebooks: "force"
+ run_in_temp: true
+ timeout: 500
+
+# Define the name of the latex output file for PDF builds
+latex:
+ latex_documents:
+ targetname: book.tex
+
+# Add a bibtex file so that we can create citations
+# bibtex_bibfiles:
+# - references.bib
+
+# Information about where the book exists on the web
+repository:
+ url: https://github.com/hsf-training/Scikit-HEP Tutorial # Online location of your book
+ path_to_book: docs # Optional path to your book, relative to the repository root
+ branch: main # Which branch of the repository should be used when creating links (optional)
+
+# Add GitHub buttons to your book
+# See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository
+html:
+ home_page_in_navbar: false
+ use_issues_button: true
+ use_repository_button: true
diff --git a/scikit_hep_tutorial/_toc.yml b/scikit_hep_tutorial/_toc.yml
new file mode 100644
index 0000000..223c3e9
--- /dev/null
+++ b/scikit_hep_tutorial/_toc.yml
@@ -0,0 +1,15 @@
+# Table of contents
+# Learn more at https://jupyterbook.org/customize/toc.html
+
+format: jb-book
+root: 00-bienvenida
+parts:
+- caption: Lecciones
+ chapters:
+ - file: 01-introduccion
+ - file: 02-uproot
+ - file: 03-ttree
+ - file: 04-awkward
+ - file: 05-histogramas
+ - file: 06-vectores-lorentz
+ - file: 07-mas-herramientas
diff --git a/scikit_hep_tutorial/fig/abstraction-layers.png b/scikit_hep_tutorial/fig/abstraction-layers.png
new file mode 100644
index 0000000..729d675
Binary files /dev/null and b/scikit_hep_tutorial/fig/abstraction-layers.png differ
diff --git a/scikit_hep_tutorial/fig/abstraction-layers.svg b/scikit_hep_tutorial/fig/abstraction-layers.svg
new file mode 100644
index 0000000..80d0779
--- /dev/null
+++ b/scikit_hep_tutorial/fig/abstraction-layers.svg
@@ -0,0 +1,867 @@
+
+
+
+
diff --git a/scikit_hep_tutorial/fig/apl-timeline.png b/scikit_hep_tutorial/fig/apl-timeline.png
new file mode 100644
index 0000000..d0fe389
Binary files /dev/null and b/scikit_hep_tutorial/fig/apl-timeline.png differ
diff --git a/scikit_hep_tutorial/fig/apl-timeline.svg b/scikit_hep_tutorial/fig/apl-timeline.svg
new file mode 100644
index 0000000..af9dfa0
--- /dev/null
+++ b/scikit_hep_tutorial/fig/apl-timeline.svg
@@ -0,0 +1,1198 @@
+
+
+
+
diff --git a/scikit_hep_tutorial/fig/array3d-highlight1.png b/scikit_hep_tutorial/fig/array3d-highlight1.png
new file mode 100644
index 0000000..e86c930
Binary files /dev/null and b/scikit_hep_tutorial/fig/array3d-highlight1.png differ
diff --git a/scikit_hep_tutorial/fig/array3d-highlight1.svg b/scikit_hep_tutorial/fig/array3d-highlight1.svg
new file mode 100644
index 0000000..6a8afe6
--- /dev/null
+++ b/scikit_hep_tutorial/fig/array3d-highlight1.svg
@@ -0,0 +1,650 @@
+
+
+
+
diff --git a/scikit_hep_tutorial/fig/array3d-highlight2.png b/scikit_hep_tutorial/fig/array3d-highlight2.png
new file mode 100644
index 0000000..d4b1cb1
Binary files /dev/null and b/scikit_hep_tutorial/fig/array3d-highlight2.png differ
diff --git a/scikit_hep_tutorial/fig/array3d-highlight2.svg b/scikit_hep_tutorial/fig/array3d-highlight2.svg
new file mode 100644
index 0000000..d685a8b
--- /dev/null
+++ b/scikit_hep_tutorial/fig/array3d-highlight2.svg
@@ -0,0 +1,650 @@
+
+
+
+
diff --git a/scikit_hep_tutorial/fig/array3d.svg b/scikit_hep_tutorial/fig/array3d.svg
new file mode 100644
index 0000000..397cd2e
--- /dev/null
+++ b/scikit_hep_tutorial/fig/array3d.svg
@@ -0,0 +1,647 @@
+
+
+
+
diff --git a/scikit_hep_tutorial/fig/bitwise-operator-parentheses.png b/scikit_hep_tutorial/fig/bitwise-operator-parentheses.png
new file mode 100644
index 0000000..9781948
Binary files /dev/null and b/scikit_hep_tutorial/fig/bitwise-operator-parentheses.png differ
diff --git a/scikit_hep_tutorial/fig/bitwise-operator-parentheses.svg b/scikit_hep_tutorial/fig/bitwise-operator-parentheses.svg
new file mode 100644
index 0000000..cb434cf
--- /dev/null
+++ b/scikit_hep_tutorial/fig/bitwise-operator-parentheses.svg
@@ -0,0 +1,509 @@
+
+
+
+
diff --git a/scikit_hep_tutorial/fig/cartoon-cartesian.png b/scikit_hep_tutorial/fig/cartoon-cartesian.png
new file mode 100644
index 0000000..694a482
Binary files /dev/null and b/scikit_hep_tutorial/fig/cartoon-cartesian.png differ
diff --git a/scikit_hep_tutorial/fig/cartoon-cartesian.svg b/scikit_hep_tutorial/fig/cartoon-cartesian.svg
new file mode 100644
index 0000000..3cb324a
--- /dev/null
+++ b/scikit_hep_tutorial/fig/cartoon-cartesian.svg
@@ -0,0 +1,618 @@
+
+
+
+
diff --git a/scikit_hep_tutorial/fig/cartoon-combinations.png b/scikit_hep_tutorial/fig/cartoon-combinations.png
new file mode 100644
index 0000000..1b9ddf8
Binary files /dev/null and b/scikit_hep_tutorial/fig/cartoon-combinations.png differ
diff --git a/scikit_hep_tutorial/fig/cartoon-combinations.svg b/scikit_hep_tutorial/fig/cartoon-combinations.svg
new file mode 100644
index 0000000..3656a1b
--- /dev/null
+++ b/scikit_hep_tutorial/fig/cartoon-combinations.svg
@@ -0,0 +1,724 @@
+
+
+
+
diff --git a/scikit_hep_tutorial/fig/scikit-hep-logos.png b/scikit_hep_tutorial/fig/scikit-hep-logos.png
new file mode 100644
index 0000000..4034e27
Binary files /dev/null and b/scikit_hep_tutorial/fig/scikit-hep-logos.png differ
diff --git a/scikit_hep_tutorial/fig/scikit-hep-logos.svg b/scikit_hep_tutorial/fig/scikit-hep-logos.svg
new file mode 100644
index 0000000..89e7512
--- /dev/null
+++ b/scikit_hep_tutorial/fig/scikit-hep-logos.svg
@@ -0,0 +1,14489 @@
+
+
+
+
diff --git a/scikit_hep_tutorial/fig/terminology.png b/scikit_hep_tutorial/fig/terminology.png
new file mode 100644
index 0000000..aa79f58
Binary files /dev/null and b/scikit_hep_tutorial/fig/terminology.png differ
diff --git a/scikit_hep_tutorial/fig/terminology.svg b/scikit_hep_tutorial/fig/terminology.svg
new file mode 100644
index 0000000..1460404
--- /dev/null
+++ b/scikit_hep_tutorial/fig/terminology.svg
@@ -0,0 +1,691 @@
+
+
+
+
diff --git a/scikit_hep_tutorial/hsf_logo.png b/scikit_hep_tutorial/hsf_logo.png
new file mode 100644
index 0000000..8a9bd8f
Binary files /dev/null and b/scikit_hep_tutorial/hsf_logo.png differ