bohrpy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bohrpy/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """
2
+ bohrpy: Una librería en Python para el modelo del átomo de Bohr.
3
+ Desarrollada para calcular niveles de energía, radios, transiciones electrónicas
4
+ y representaciones gráficas de átomos hidrogenoides.
5
+ """
6
+
7
+ # Importamos la clase principal para facilitar su acceso
8
+ from .bohr import BohrAtom
9
+
10
+ # Importamos las constantes físicas
11
+ from .constants import H_PLANCK, C_LIGHT, E_CHARGE, M_ELECTRON, A_0
12
+
13
+ # Importamos las gráficas para usarlas sin entrar al módulo plots
14
+ from .plots import plot_energy_levels, plot_orbits
15
+
16
+ # Definición de la versión de la librería (ideal para publicar en PyPI)
17
+ __version__ = "0.1.0"
18
+
19
+ # Limitamos qué se importa cuando alguien usa 'from bohrpy import *'
20
+ __all__ = [
21
+ "BohrAtom",
22
+ "H_PLANCK",
23
+ "C_LIGHT",
24
+ "E_CHARGE",
25
+ "M_ELECTRON",
26
+ "A_0",
27
+ "plot_energy_levels",
28
+ "plot_orbits",
29
+ ]
bohrpy/bohr.py ADDED
@@ -0,0 +1,84 @@
1
+ from .constants import H_PLANCK, C_LIGHT, E_CHARGE, A_0
2
+
3
+ class BohrAtom:
4
+ """
5
+ Clase que representa un átomo hidrogenoide según el modelo de Bohr.
6
+ Encapsula los parámetros físicos y métodos para cálculos y gráficos.
7
+ """
8
+
9
+ def __init__(self, Z=1):
10
+ """
11
+ Inicializa el átomo con un número atómico Z.
12
+ Por defecto, Z=1 (Hidrógeno).
13
+ """
14
+ if not isinstance(Z, int) or Z < 1:
15
+ raise ValueError("El número atómico Z debe ser un entero positivo.")
16
+ self.Z = Z
17
+ # Energía del estado fundamental del Hidrógeno en eV (aprox -13.6 eV)
18
+ self.E1_H = -13.605693
19
+
20
+ def energy_level(self, n):
21
+ """
22
+ Calcula la energía del electrón en el nivel n en electrón-voltios (eV).
23
+ """
24
+ if n < 1:
25
+ raise ValueError("El número cuántico principal n debe ser >= 1.")
26
+ return self.E1_H * (self.Z**2) / (n**2)
27
+
28
+ def orbit_radius(self, n):
29
+ """
30
+ Calcula el radio de la órbita para el nivel n en metros (m).
31
+ """
32
+ if n < 1:
33
+ raise ValueError("El número cuántico principal n debe ser >= 1.")
34
+ return A_0 * (n**2) / self.Z
35
+
36
+ def transition(self, n_initial, n_final):
37
+ """
38
+ Calcula la transición electrónica entre dos niveles.
39
+ Retorna la diferencia de energía (eV), la frecuencia (Hz) y la longitud de onda (m) del fotón emitido o absorbido.
40
+ """
41
+ if n_initial == n_final:
42
+ raise ValueError("Los niveles inicial y final deben ser diferentes.")
43
+
44
+ e_i = self.energy_level(n_initial)
45
+ e_f = self.energy_level(n_final)
46
+
47
+ # Diferencia de energía en eV y conversión a Julios
48
+ delta_E_eV = abs(e_f - e_i)
49
+ delta_E_J = delta_E_eV * E_CHARGE
50
+
51
+ # Frecuencia: f = E / h
52
+ frequency = delta_E_J / H_PLANCK
53
+
54
+ # Longitud de onda: lambda = c / f
55
+ wavelength = C_LIGHT / frequency
56
+
57
+ return {
58
+ "delta_E_eV": delta_E_eV,
59
+ "frequency_Hz": frequency,
60
+ "wavelength_m": wavelength,
61
+ "type": "emisión" if n_initial > n_final else "absorción"
62
+ }
63
+
64
+ def plot_energy_levels(self, max_n=5, show=True, save_path=None):
65
+ """
66
+ Representa los niveles de energía y las transiciones hasta max_n.
67
+
68
+ El dibujo vive en plots.py. Este método solo expone esa gráfica
69
+ desde el átomo.
70
+ """
71
+ from .plots import plot_energy_levels
72
+
73
+ return plot_energy_levels(self, max_n=max_n, show=show, save_path=save_path)
74
+
75
+ def plot_orbits(self, max_n=4, show=True, save_path=None):
76
+ """
77
+ Dibuja las órbitas electrónicas en el plano hasta el nivel max_n.
78
+
79
+ El dibujo vive en plots.py. Este método solo expone esa gráfica
80
+ desde el átomo.
81
+ """
82
+ from .plots import plot_orbits
83
+
84
+ return plot_orbits(self, max_n=max_n, show=show, save_path=save_path)
bohrpy/constants.py ADDED
@@ -0,0 +1,14 @@
1
+ """
2
+ Constantes físicas fundamentales para el modelo del átomo de Bohr.
3
+ Valores basados en el Sistema Internacional (SI) y factores de conversión.
4
+ """
5
+
6
+ H_PLANCK = 6.62607015e-34 # Constante de Planck (J·s)
7
+ C_LIGHT = 299792458 # Velocidad de la luz en el vacío (m/s)
8
+ E_CHARGE = 1.602176634e-19 # Carga elemental o del electrón (C)
9
+ M_ELECTRON = 9.1093837015e-31 # Masa del electrón (kg)
10
+ EPSILON_0 = 8.8541878128e-12 # Permitividad del vacío (F/m)
11
+
12
+ # Constantes derivadas útiles
13
+ K_COULOMB = 1 / (4 * 3.141592653589793 * EPSILON_0) # Constante de Coulomb (N·m²/C²)
14
+ A_0 = 5.29177210903e-11 # Radio de Bohr (m)
bohrpy/plots.py ADDED
@@ -0,0 +1,193 @@
1
+ """Gráficas del modelo de Bohr: niveles de energía y órbitas."""
2
+
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+
6
+ # Nombre y color de cada serie, según el nivel inferior de la transición.
7
+ SERIES = {
8
+ 1: ("Lyman", "#1f77b4"),
9
+ 2: ("Balmer", "#d62728"),
10
+ 3: ("Paschen", "#2ca02c"),
11
+ 4: ("Brackett", "#9467bd"),
12
+ 5: ("Pfund", "#8c564b"),
13
+ }
14
+
15
+
16
+ def _validar_max_n(max_n):
17
+ """Comprueba que el nivel máximo sea un entero mayor o igual que 1."""
18
+ if not isinstance(max_n, int) or isinstance(max_n, bool) or max_n < 1:
19
+ raise ValueError("max_n debe ser un entero mayor o igual que 1.")
20
+ return max_n
21
+
22
+
23
+ def _estilo_serie(n_final):
24
+ """Devuelve el nombre y el color de la serie que termina en n_final."""
25
+ if n_final in SERIES:
26
+ return SERIES[n_final]
27
+ return (f"hacia n={n_final}", "#7f7f7f")
28
+
29
+
30
+ def _guardar_o_mostrar(fig, show, save_path):
31
+ """Guarda la figura si hay ruta y la muestra si show es verdadero."""
32
+ if save_path is not None:
33
+ fig.savefig(save_path, dpi=150, bbox_inches="tight")
34
+ if show:
35
+ plt.show()
36
+
37
+
38
+ def plot_energy_levels(atom, max_n=5, show=True, save_path=None):
39
+ """Dibuja los niveles de energía y las transiciones de emisión.
40
+
41
+ Cada línea horizontal es un nivel n. Las flechas bajan del nivel
42
+ inicial al final y se agrupan por serie (Lyman, Balmer, Paschen...).
43
+ Junto a cada flecha va la longitud de onda del fotón, en nanómetros.
44
+
45
+ Parameters
46
+ ----------
47
+ atom : BohrAtom
48
+ Átomo hidrogenoide cuyos niveles se van a dibujar.
49
+ max_n : int
50
+ Nivel más alto que entra en la figura.
51
+ show : bool
52
+ Si es verdadero, abre la ventana de la gráfica.
53
+ save_path : str o Path, opcional
54
+ Ruta donde se guarda la imagen. Si es None, no se guarda.
55
+
56
+ Returns
57
+ -------
58
+ tuple
59
+ La figura y los ejes de Matplotlib.
60
+ """
61
+ _validar_max_n(max_n)
62
+
63
+ fig, ax = plt.subplots(figsize=(8, 8))
64
+ energia_minima = atom.energy_level(1)
65
+
66
+ for n in range(1, max_n + 1):
67
+ energia = atom.energy_level(n)
68
+ ax.hlines(energia, 0, 1, colors="#1f4e79", linewidth=2)
69
+ ax.text(
70
+ 1.04,
71
+ energia,
72
+ f"n = {n} {energia:.2f} eV",
73
+ va="center",
74
+ fontsize=9,
75
+ )
76
+
77
+ # Cada transición ocupa su propia x. Las más largas van a la izquierda,
78
+ # donde hay más espacio vertical para la etiqueta.
79
+ transiciones = [
80
+ (n_inicial, n_final)
81
+ for n_final in range(1, max_n)
82
+ for n_inicial in range(n_final + 1, max_n + 1)
83
+ ]
84
+ transiciones.sort(
85
+ key=lambda niveles: atom.energy_level(niveles[0]) - atom.energy_level(niveles[1])
86
+ )
87
+ caja = {"boxstyle": "round,pad=0.12", "fc": "white", "ec": "none", "alpha": 0.9}
88
+
89
+ for indice, (n_inicial, n_final) in enumerate(transiciones):
90
+ _, color = _estilo_serie(n_final)
91
+ x = 0.06 + 0.88 * (indice + 0.5) / len(transiciones)
92
+ energia_inicial = atom.energy_level(n_inicial)
93
+ energia_final = atom.energy_level(n_final)
94
+ ax.annotate(
95
+ "",
96
+ xy=(x, energia_final),
97
+ xytext=(x, energia_inicial),
98
+ arrowprops={"arrowstyle": "-|>", "color": color, "lw": 1.4},
99
+ )
100
+ transicion = atom.transition(n_inicial, n_final)
101
+ longitud_nm = transicion["wavelength_m"] * 1e9
102
+ salto = abs(energia_inicial - energia_final)
103
+ if salto < 0.08 * abs(energia_minima):
104
+ y_texto = energia_inicial + 0.045 * abs(energia_minima)
105
+ va = "bottom"
106
+ else:
107
+ y_texto = (energia_inicial + energia_final) / 2
108
+ va = "center"
109
+ ax.text(
110
+ x,
111
+ y_texto,
112
+ f"{longitud_nm:.0f} nm",
113
+ color=color,
114
+ fontsize=7,
115
+ ha="center",
116
+ va=va,
117
+ bbox=caja,
118
+ )
119
+
120
+ for n_final in range(1, max_n):
121
+ nombre, color = _estilo_serie(n_final)
122
+ ax.plot([], [], color=color, label=nombre)
123
+
124
+ ax.set_xlim(-0.05, 1.62)
125
+ ax.set_ylim(energia_minima * 1.12, abs(energia_minima) * 0.08)
126
+ ax.set_xticks([])
127
+ ax.set_ylabel("Energía (eV)")
128
+ ax.set_title(f"Niveles de energía y transiciones (Z = {atom.Z})")
129
+ ax.grid(axis="y", linestyle="--", alpha=0.35)
130
+ ax.legend(title="Serie", loc="lower right", frameon=True)
131
+ fig.tight_layout()
132
+ _guardar_o_mostrar(fig, show, save_path)
133
+ return fig, ax
134
+
135
+
136
+ def plot_orbits(atom, max_n=4, show=True, save_path=None):
137
+ """Dibuja las órbitas circulares del modelo de Bohr en el plano.
138
+
139
+ El núcleo queda en el origen. El radio se muestra en ångströms
140
+ para que la escala sea legible, y un punto marca al electrón
141
+ sobre el eje x de cada órbita.
142
+
143
+ Parameters
144
+ ----------
145
+ atom : BohrAtom
146
+ Átomo hidrogenoide cuyas órbitas se van a dibujar.
147
+ max_n : int
148
+ Nivel más alto que entra en la figura.
149
+ show : bool
150
+ Si es verdadero, abre la ventana de la gráfica.
151
+ save_path : str o Path, opcional
152
+ Ruta donde se guarda la imagen. Si es None, no se guarda.
153
+
154
+ Returns
155
+ -------
156
+ tuple
157
+ La figura y los ejes de Matplotlib.
158
+ """
159
+ _validar_max_n(max_n)
160
+
161
+ fig, ax = plt.subplots(figsize=(7.2, 7))
162
+ angulo = np.linspace(0, 2 * np.pi, 400)
163
+ colores = plt.cm.viridis(np.linspace(0.15, 0.9, max_n))
164
+
165
+ ax.scatter(
166
+ [0],
167
+ [0],
168
+ s=130,
169
+ c="#c0392b",
170
+ zorder=5,
171
+ label=f"Núcleo (Z = {atom.Z})",
172
+ )
173
+
174
+ for n, color in zip(range(1, max_n + 1), colores):
175
+ radio_angstrom = atom.orbit_radius(n) * 1e10
176
+ ax.plot(
177
+ radio_angstrom * np.cos(angulo),
178
+ radio_angstrom * np.sin(angulo),
179
+ color=color,
180
+ linewidth=1.8,
181
+ label=f"n = {n} r = {radio_angstrom:.3f} Å",
182
+ )
183
+ ax.scatter([radio_angstrom], [0], s=30, color=color, zorder=4)
184
+
185
+ ax.set_aspect("equal")
186
+ ax.set_xlabel("x (Å)")
187
+ ax.set_ylabel("y (Å)")
188
+ ax.set_title(f"Órbitas del modelo de Bohr (Z = {atom.Z})")
189
+ ax.grid(True, linestyle="--", alpha=0.4)
190
+ ax.legend(loc="upper left", bbox_to_anchor=(1.02, 1), frameon=False)
191
+ fig.tight_layout()
192
+ _guardar_o_mostrar(fig, show, save_path)
193
+ return fig, ax
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Isabel Nieto y Kevin Osorio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.1
2
+ Name: bohrpy
3
+ Version: 0.1.0
4
+ Summary: Librería para cálculos y visualización del modelo atómico de Bohr
5
+ Author: Isabel Nieto, Kevin Osorio
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/isabelnieto900/bohrpy
8
+ Requires-Python: >=3.9
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: numpy==1.26.4
12
+ Requires-Dist: matplotlib==3.6.3
13
+ Provides-Extra: test
14
+ Requires-Dist: pytest==9.2.0; extra == "test"
15
+
16
+ # bohrpy
17
+
18
+ Librería en Python del modelo atómico de Bohr para el hidrógeno y átomos hidrogenoides. Calcula niveles de energía, radios de órbita y transiciones electrónicas, y dibuja esos resultados.
19
+
20
+ ## Instalación
21
+
22
+ Desde la raíz del repositorio:
23
+
24
+ ```bash
25
+ python -m venv venv
26
+ source venv/bin/activate
27
+ pip install -e ".[test]"
28
+ ```
29
+
30
+ El paquete pide estas versiones, que son las usadas en el proyecto:
31
+
32
+ - `numpy==1.26.4`
33
+ - `matplotlib==3.6.3`
34
+ - `pytest==9.2.0` para las pruebas
35
+
36
+ ## Uso básico
37
+
38
+ ```python
39
+ from bohrpy import BohrAtom
40
+
41
+ hidrogeno = BohrAtom(Z=1)
42
+
43
+ print(hidrogeno.energy_level(1)) # energía del nivel n, en eV
44
+ print(hidrogeno.orbit_radius(1)) # radio de la órbita, en metros
45
+
46
+ transicion = hidrogeno.transition(3, 2)
47
+ print(transicion["delta_E_eV"])
48
+ print(transicion["frequency_Hz"])
49
+ print(transicion["wavelength_m"])
50
+ print(transicion["type"]) # "emisión" o "absorción"
51
+
52
+ hidrogeno.plot_energy_levels(max_n=5)
53
+ hidrogeno.plot_orbits(max_n=4)
54
+ ```
55
+
56
+ `Z` es el número atómico. Con `Z=1` se obtiene el hidrógeno y con `Z=2` el He+.
57
+
58
+ Para guardar una figura sin abrir una ventana:
59
+
60
+ ```python
61
+ hidrogeno.plot_energy_levels(max_n=5, show=False, save_path="niveles.png")
62
+ hidrogeno.plot_orbits(max_n=4, show=False, save_path="orbitas.png")
63
+ ```
64
+
65
+ ## Ejemplo
66
+
67
+ ```bash
68
+ python examples/ejemplo_uso.py
69
+ ```
70
+
71
+ Imprime los niveles del hidrógeno, la línea de Balmer n = 3 → n = 2 y una absorción n = 1 → n = 2. También guarda las figuras en `examples/figuras/`.
72
+
73
+ ## Pruebas
74
+
75
+ ```bash
76
+ pytest
77
+ ```
78
+
79
+ ## Autores
80
+
81
+ - Isabel Nieto
82
+ - Kevin Osorio
@@ -0,0 +1,9 @@
1
+ bohrpy/__init__.py,sha256=cdYvsFXqdvl0dlpVNyk7slFL0FLwW4aGccfX5S06Ga8,844
2
+ bohrpy/bohr.py,sha256=Q7qqjWmaFH_XjYoQQKtC1r1kECe4IBRdkh_baP_musA,2988
3
+ bohrpy/constants.py,sha256=dvZfLK6zalws7OqP-pKc60lmbtyW8GIiSIDyeOrq0b4,647
4
+ bohrpy/plots.py,sha256=0wkLk3SacltHG1O-YnufS-7tLPmrPV9tg0fwuZV6i-w,6256
5
+ bohrpy-0.1.0.dist-info/LICENSE,sha256=0dyNrY5V934bq3ZZg_qWXYUjtUsSD0GZvqbFcbcezsA,1084
6
+ bohrpy-0.1.0.dist-info/METADATA,sha256=BgVHywMeAcufNucbfuGBCSRRZOXBpLFMkeBtSgE7SXk,2016
7
+ bohrpy-0.1.0.dist-info/WHEEL,sha256=EWGgdj_eH-g9dwsWZOTG0kmGucEuKPxGzOKD-jAVUHs,92
8
+ bohrpy-0.1.0.dist-info/top_level.txt,sha256=51jhWjESZlWunATjNLA2XcOZoyou2QZKu7Qc6VeR8sI,7
9
+ bohrpy-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.48.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ bohrpy