Questo programma visualizza l'evoluzione dei numeri primi attraverso il modello **ADEC (Autopoietic Dynamic Entropic Clock)**. A differenza degli algoritmi tradizionali, i numeri primi emergono spontaneamente da un sistema di cicli in continua evoluzione, non da un calcolo predefinito.
Il programma genera due visualizzazioni interconnesse:
1. **La Dinamica del Sistema:** Un grafico che mostra il **livello di coerenza** del sistema nel tempo. I picchi di coerenza coincidono esattamente con la generazione di un nuovo numero primo, dimostrando che questi eventi si verificano in momenti di "massima armonia" interna.
2. **La Struttura Chirale:** Una visualizzazione del grafo che si forma attorno a un numero di partenza $n$, mostrando le complesse relazioni tra le coppie $(n-k)$ e $(n+k)$. Questa rete rivela una **chiralità** intrinseca, manifestata da un **gradiente numerico** che riflette un'asimmetria naturale nei valori dei numeri.
In sintesi, il programma unisce la dinamica del processo di generazione con la struttura geometrica che ne emerge, offrendo una visione completa e interconnessa dei numeri primi.
import networkx as nx
import matplotlib.pyplot as plt
import math
from collections import deque
# --- Classi del Modello ADEC ---
class ADEC:
"""Simula il sistema ADEC per generare numeri primi emergenti
basandosi solo sulla logica dei cicli."""
def __init__(self):
self.counter = 2
self.matrices = [self.Matrix(2)]
self.primes = []
self.coherence_history = []
self.prime_steps = []
def get_coherence(self):
"""Calcola il livello di coerenza del sistema."""
coherence = sum(1 - (m.position / m.size) for m in self.matrices)
return coherence
def step(self):
"""Avanza di un passo e aggiorna il sistema."""
self.counter += 1
for m in self.matrices:
m.update_position()
is_generation_time = all(m.is_first_row_empty() for m in self.matrices)
self.coherence_history.append(self.get_coherence())
if is_generation_time:
self.primes.append(self.counter)
new_matrix = self.Matrix(self.counter)
self.matrices.append(new_matrix)
self.prime_steps.append(self.counter - 1)
class Matrix:
"""Una matrice ciclica semplificata con un contatore interno."""
def __init__(self, size):
self.size = size
self.position = 0
def update_position(self):
self.position = (self.position + 1) % self.size
def is_first_row_empty(self):
return self.position != 0
# --- Funzioni di Generazione e Visualizzazione ---
def generate_chiral_structure(n, max_k, adec_sim_steps):
"""
Genera la struttura chirale usando i primi emergenti da un sistema ADEC.
Include la misurazione del gradiente numerico.
Argomenti:
n (int): Il numero centrale.
max_k (int): Il raggio di ricerca per le coppie (n-k, n+k).
adec_sim_steps (int): Numero di passi di simulazione per l'ADEC.
Ritorna:
Un grafo (networkx.Graph), le liste dei nodi primi e l'oggetto ADEC.
"""
# 1. Simula il sistema ADEC per generare i numeri primi
adec = ADEC()
for _ in range(adec_sim_steps):
adec.step()
generated_primes = set(adec.primes)
print(f"Primi emergenti dall'ADEC (dopo {adec_sim_steps} passi): {sorted(list(generated_primes))}")
# 2. Inizializza il grafo e il nodo centrale
G = nx.Graph()
G.add_node(n, color='red', size=800, label=str(n))
# 3. Costruisce la struttura chirale
nk_primes = []
npk_primes = []
for k in range(1, max_k + 1):
nk = n - k
npk = n + k
if nk > 0 and nk in generated_primes:
G.add_node(nk, color='blue', size=300, label=str(nk))
G.add_edge(n, nk, type='coprime')
nk_primes.append(nk)
if npk in generated_primes:
G.add_node(npk, color='green', size=300, label=str(npk))
G.add_edge(n, npk, type='coprime')
npk_primes.append(npk)
for k in range(1, max_k + 1):
nk = n - k
npk = n + k
if nk > 0 and not G.has_node(nk):
G.add_node(nk, color='lightgray', size=150, label=str(nk))
G.add_edge(n, nk, style='dotted')
if not G.has_node(npk):
G.add_node(npk, color='lightgray', size=150, label=str(npk))
G.add_edge(n, npk, style='dotted')
return G, nk_primes, npk_primes, adec
def chiral_layout(G, n, radius_factor=1.0, angle_factor=0.1):
"""
Calcola un layout di nodi che emula una struttura chirale.
"""
pos = {n: (0, 0)}
n_minus_nodes = sorted([node for node in G.nodes() if node < n and G.has_edge(n, node)], reverse=True)
n_plus_nodes = sorted([node for node in G.nodes() if node > n and G.has_edge(n, node)])
max_dist = max(max(n_plus_nodes) - n if n_plus_nodes else 0, n - min(n_minus_nodes) if n_minus_nodes else 0, 1)
for _, node in enumerate(n_minus_nodes):
k_val = n - node
radius = k_val * radius_factor / max_dist
angle = -k_val * angle_factor
x = radius * math.cos(angle)
y = radius * math.sin(angle)
pos[node] = (x - 0.5 * radius_factor, y)
for _, node in enumerate(n_plus_nodes):
k_val = node - n
radius = k_val * radius_factor / max_dist
angle = k_val * angle_factor
x = radius * math.cos(angle)
y = radius * math.sin(angle)
pos[node] = (x + 0.5 * radius_factor, y)
return pos
def visualize_graph_and_coherence(G, n, adec):
"""Visualizza il grafo e il grafico della coerenza."""
plt.style.use('dark_background')
fig, axs = plt.subplots(1, 2, figsize=(20, 10))
fig.suptitle(f"Analisi Dinamica e Strutturale di ADEC per n = {n}", fontsize=22, color='white')
# Grafico della Coerenza del Sistema
axs[0].plot(adec.coherence_history, color='cyan', linewidth=2)
axs[0].set_title("Evoluzione della Coerenza del Sistema", fontsize=16, color='white')
axs[0].set_xlabel("Passi di Simulazione", fontsize=12, color='white')
axs[0].set_ylabel("Livello di Coerenza", fontsize=12, color='white')
for prime_step in adec.prime_steps:
axs[0].axvline(x=prime_step, color='red', linestyle='--', linewidth=1.5, alpha=0.7)
axs[0].text(prime_step, max(adec.coherence_history) * 0.9, f'{adec.primes[adec.prime_steps.index(prime_step)]}', color='red', ha='center', fontsize=10)
axs[0].tick_params(colors='white')
axs[0].spines['bottom'].set_color('white')
axs[0].spines['left'].set_color('white')
# Visualizzazione del Grafo
pos = chiral_layout(G, n, radius_factor=3.0, angle_factor=0.5)
node_colors = [G.nodes[node]['color'] for node in G.nodes()]
node_sizes = [G.nodes[node]['size'] for node in G.nodes()]
node_labels = {node: G.nodes[node]['label'] for node in G.nodes()}
nx.draw_networkx_nodes(G, pos, ax=axs[1], node_color=node_colors, node_size=node_sizes, alpha=0.9)
nx.draw_networkx_labels(G, pos, ax=axs[1], labels=node_labels, font_size=9, font_weight='bold', font_color='white')
solid_edges = [(u, v) for u, v, data in G.edges(data=True) if data.get('style') != 'dotted']
dotted_edges = [(u, v) for u, v, data in G.edges(data=True) if data.get('style') == 'dotted']
nx.draw_networkx_edges(G, pos, ax=axs[1], edgelist=solid_edges, width=1.5, alpha=0.7, edge_color='gray')
nx.draw_networkx_edges(G, pos, ax=axs[1], edgelist=dotted_edges, width=0.8, alpha=0.5, edge_color='lightgray', style='dotted')
axs[1].set_title("Struttura Chirale Numerica", fontsize=16, color='white')
axs[1].axis('off')
plt.tight_layout()
plt.show()
# --- Esecuzione del Programma ---
if __name__ == "__main__":
try:
n_input = input("Inserisci un numero intero per n (es. 31, 60): ")
n = int(n_input)
adec_steps = n * 5 # Un valore approssimativo per generare abbastanza primi
max_k = n - 2
G, nk_primes, npk_primes, adec_obj = generate_chiral_structure(n, max_k, adec_steps)
print("\n--- Analisi della Struttura ---")
print(f"Nodi (n-k) primi: {sorted(nk_primes)}")
print(f"Numero di nodi (n-k) primi: {len(nk_primes)}")
print(f"Nodi (n+k) primi: {sorted(npk_primes)}")
print(f"Numero di nodi (n+k) primi: {len(npk_primes)}")
visualize_graph_and_coherence(G, n, adec_obj)
except ValueError:
print("Input non valido. Inserisci un numero intero.")
except Exception as e:
print(f"Si è verificato un errore: {e}")
import networkx as nx
import matplotlib.pyplot as plt
import math
# --- Classi del Modello ADEC ---
class ADEC:
"""Simula il sistema ADEC per generare numeri primi emergenti
basandosi solo sulla logica dei cicli."""
def __init__(self):
self.counter = 2
self.matrices = [self.Matrix(2)]
self.primes = []
def step(self):
"""Avanza di un passo e aggiorna il sistema."""
self.counter += 1
for m in self.matrices:
m.update_position()
is_generation_time = all(m.is_first_row_empty() for m in self.matrices)
if is_generation_time:
self.primes.append(self.counter)
new_matrix = self.Matrix(self.counter)
self.matrices.append(new_matrix)
class Matrix:
"""Una matrice ciclica semplificata con un contatore interno."""
def __init__(self, size):
self.size = size
self.position = 0
def update_position(self):
self.position = (self.position + 1) % self.size
def is_first_row_empty(self):
return self.position != 0
# --- Funzioni di Generazione e Analisi ---
def generate_chiral_structure(n, max_k, adec_sim_steps):
"""
Genera la struttura chirale usando i primi emergenti da un sistema ADEC.
Include la misurazione del gradiente numerico.
Argomenti:
n (int): Il numero centrale.
max_k (int): Il raggio di ricerca per le coppie (n-k, n+k).
adec_sim_steps (int): Numero di passi di simulazione per l'ADEC.
Ritorna:
Un grafo (networkx.Graph), e le liste dei nodi (n-k) e (n+k) primi.
"""
# 1. Simula il sistema ADEC per generare i numeri primi
adec = ADEC()
for _ in range(adec_sim_steps):
adec.step()
generated_primes = set(adec.primes)
print(f"Primi emergenti dall'ADEC (dopo {adec_sim_steps} passi): {sorted(list(generated_primes))}")
# 2. Inizializza il grafo e il nodo centrale
G = nx.Graph()
G.add_node(n, color='red', size=800, label=str(n))
# 3. Costruisce la struttura chirale e misura il gradiente
nk_primes = []
npk_primes = []
# Misura dei pesi
weight_left = 0
weight_right = 0
for k in range(1, max_k + 1):
nk = n - k
npk = n + k
# Aggiunge nodi e archi per la coppia (n-k) se primo
if nk > 0 and nk in generated_primes:
G.add_node(nk, color='blue', size=300, label=str(nk))
G.add_edge(n, nk, type='coprime')
nk_primes.append(nk)
weight_left += nk
# Aggiunge nodi e archi per la coppia (n+k) se primo
if npk in generated_primes:
G.add_node(npk, color='green', size=300, label=str(npk))
G.add_edge(n, npk, type='coprime')
npk_primes.append(npk)
weight_right += npk
# Aggiunge nodi non-primi per completezza visiva e misurazione del gradiente
for k in range(1, max_k + 1):
nk = n - k
npk = n + k
if nk > 0 and not G.has_node(nk):
G.add_node(nk, color='lightgray', size=150, label=str(nk))
G.add_edge(n, nk, style='dotted')
# Aggiunge il peso dei nodi non primi per un calcolo completo del gradiente
weight_left += nk
if not G.has_node(npk):
G.add_node(npk, color='lightgray', size=150, label=str(npk))
G.add_edge(n, npk, style='dotted')
# Aggiunge il peso dei nodi non primi
weight_right += npk
# Calcola il gradiente numerico totale
numeric_gradient = weight_right - weight_left
# Aggiunge gli attributi di peso e gradiente al nodo centrale per la visualizzazione
G.nodes[n]['weight_left'] = weight_left
G.nodes[n]['weight_right'] = weight_right
G.nodes[n]['numeric_gradient'] = numeric_gradient
return G, nk_primes, npk_primes
def chiral_layout(G, n, radius_factor=1.0, angle_factor=0.1):
"""
Calcola un layout di nodi che emula una struttura chirale.
"""
pos = {n: (0, 0)}
n_minus_nodes = sorted([node for node in G.nodes() if node < n and G.has_edge(n, node)], reverse=True)
n_plus_nodes = sorted([node for node in G.nodes() if node > n and G.has_edge(n, node)])
max_dist = max(max(n_plus_nodes) - n if n_plus_nodes else 0, n - min(n_minus_nodes) if n_minus_nodes else 0, 1)
for _, node in enumerate(n_minus_nodes):
k_val = n - node
radius = k_val * radius_factor / max_dist
angle = -k_val * angle_factor
x = radius * math.cos(angle)
y = radius * math.sin(angle)
pos[node] = (x - 0.5 * radius_factor, y)
for _, node in enumerate(n_plus_nodes):
k_val = node - n
radius = k_val * radius_factor / max_dist
angle = k_val * angle_factor
x = radius * math.cos(angle)
y = radius * math.sin(angle)
pos[node] = (x + 0.5 * radius_factor, y)
return pos
def visualize_graph(G, n):
"""Visualizza il grafo con i nodi colorati e le etichette."""
plt.figure(figsize=(15, 12))
pos = chiral_layout(G, n, radius_factor=3.0, angle_factor=0.5)
node_colors = [G.nodes[node]['color'] for node in G.nodes()]
node_sizes = [G.nodes[node]['size'] for node in G.nodes()]
node_labels = {node: G.nodes[node]['label'] for node in G.nodes()}
nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=node_sizes, alpha=0.9)
nx.draw_networkx_labels(G, pos, labels=node_labels, font_size=9, font_weight='bold')
solid_edges = [(u, v) for u, v, data in G.edges(data=True) if data.get('style') != 'dotted']
nx.draw_networkx_edges(G, pos, edgelist=solid_edges, width=1.5, alpha=0.7, edge_color='gray')
dotted_edges = [(u, v) for u, v, data in G.edges(data=True) if data.get('style') == 'dotted']
nx.draw_networkx_edges(G, pos, edgelist=dotted_edges, width=0.8, alpha=0.5, edge_color='lightgray', style='dotted')
plt.title(f"Struttura Chirale Numerica per n = {n}", fontsize=18)
plt.axis('off')
plt.show()
# --- Esecuzione del Programma ---
if __name__ == "__main__":
try:
n_input = input("Inserisci un numero intero per n (es. 31, 60): ")
n = int(n_input)
adec_steps = n * 5
max_k = n - 2
G, nk_primes, npk_primes = generate_chiral_structure(n, max_k, adec_steps)
print("\n--- Analisi della Struttura ---")
print(f"Nodi (n-k) primi: {sorted(nk_primes)}")
print(f"Numero di nodi (n-k) primi: {len(nk_primes)}")
print(f"Nodi (n+k) primi: {sorted(npk_primes)}")
print(f"Numero di nodi (n+k) primi: {len(npk_primes)}")
# Misurazione del gradiente numerico
weight_left_total = G.nodes[n].get('weight_left', 0)
weight_right_total = G.nodes[n].get('weight_right', 0)
gradient_total = G.nodes[n].get('numeric_gradient', 0)
print("\n--- Misurazione del Gradiente Numerico ---")
print(f"Peso totale (n-k): {weight_left_total}")
print(f"Peso totale (n+k): {weight_right_total}")
print(f"Gradiente Numerico (n+k - n-k): {gradient_total}")
print("------------------------------------------")
visualize_graph(G, n)
except ValueError:
print("Input non valido. Inserisci un numero intero.")
except Exception as e:
print(f"Si è verificato un errore: {e}")
Contesto e obiettivi
La simulazione adottata implementa un modello ADEC avanzato che rappresenta un sistema complesso con nodi capaci di accumulare energia/informazione, interagire tra loro e rigenerarsi parzialmente, con capacità variabili.
Si è voluto studiare la dinamica di crescita, saturazione e collasso degli agenti/nodi in un sistema con flusso energetico variabile e interazioni casuali.
Principali risultati qualitativi
• I nodi iniziano la simulazione completamente vivi, accumulando energia con flusso variabile.
• La rigenerazione limita parzialmente l’accumulo ma non impedisce la progressiva crescita.
• Intorno al 23°-24° passo iniziano a morire i primi nodi: il sistema entra in una fase di progressivo collasso.
• Al passo 34 la totalità dei nodi risulta "morta", segno di superamento della capacità organizzativa del sistema.
• L'energia media cresce fino al massimo ma dopo la morte dei nodi rimane stabile al massimo consentito.
• Le interazioni modulari e casuali contribuiscono a questa evoluzione dinamica non del tutto lineare.
Interpretazione e implicazioni
Il modello concretizza i principi teorici ADEC relativi ai limiti di capacità e velocità di organizzazione/crescita dell'informazione/energia in sistemi complessi.
La simulazione dimostra che in sistemi reali è possibile ritardare il collasso attraverso parametri di rigenerazione e variabilità, ma la soglia organizzativa resta un limite invalicabile.
import numpy as np
import matplotlib.pyplot as plt
class Node:
def __init__(self, capacity):
self.capacity = capacity
self.energy = 0.0
self.alive = True
def accumulate(self, inflow):
if not self.alive:
return
self.energy += inflow
if self.energy > self.capacity:
self.alive = False
self.energy = self.capacity
def regenerate(self, regen_rate):
if self.alive:
self.energy = max(0, self.energy - regen_rate)
def interact(self, exchange):
# Semplice scambio di energia positivo o negativo
if not self.alive:
return 0
delta = np.clip(exchange, -self.energy, self.capacity - self.energy)
self.energy += delta
if self.energy <= 0:
self.alive = False
self.energy = 0
elif self.energy >= self.capacity:
self.energy = self.capacity
return delta
class SystemADEC:
def __init__(self, n_nodes, base_capacity):
# Capacità random intorno a base_capacity (+-20%)
self.nodes = [Node(np.random.uniform(0.8*base_capacity, 1.2*base_capacity)) for _ in range(n_nodes)]
self.time = 0
self.history = []
def step(self, base_inflow, regen_rate):
# Variabilità di inflow (+-10%)
inflows = [base_inflow * np.random.uniform(0.9, 1.1) for _ in self.nodes]
# Accumulo energia
for node, inflow in zip(self.nodes, inflows):
node.accumulate(inflow)
# Interazioni casuali tra nodi (scambio energia)
for _ in range(len(self.nodes)//2):
i, j = np.random.choice(len(self.nodes), 2, replace=False)
exchange = np.random.uniform(-0.1, 0.1)
delta_i = self.nodes[i].interact(exchange)
self.nodes[j].interact(-delta_i)
# Rigenerazione
for node in self.nodes:
node.regenerate(regen_rate)
self.time += 1
self.record_state()
def record_state(self):
energies = [node.energy for node in self.nodes]
alive_count = sum(node.alive for node in self.nodes)
avg_energy = np.mean(energies)
min_energy = np.min(energies)
max_energy = np.max(energies)
self.history.append({
'time': self.time,
'alive': alive_count,
'avg_energy': avg_energy,
'min_energy': min_energy,
'max_energy': max_energy
})
print(f"Step {self.time}: Nodi vivi = {alive_count}, Energia media = {avg_energy:.2f}, Min = {min_energy:.2f}, Max = {max_energy:.2f}")
def plot(self):
times = [h['time'] for h in self.history]
alive_counts = [h['alive'] for h in self.history]
avg_energies = [h['avg_energy'] for h in self.history]
fig, ax1 = plt.subplots()
color = 'tab:blue'
ax1.set_xlabel('Step temporale')
ax1.set_ylabel('Nodi vivi', color=color)
ax1.plot(times, alive_counts, color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx()
color = 'tab:red'
ax2.set_ylabel('Energia media', color=color)
ax2.plot(times, avg_energies, color=color)
ax2.tick_params(axis='y', labelcolor=color)
plt.title('Modello ADEC Avanzato: Nodi vivi e energia media nel tempo')
fig.tight_layout()
plt.show()
# Parametri simulazione
num_nodes = 50
base_capacity = 10.0
base_inflow = 0.4
regen_rate = 0.05
max_steps = 60
system = SystemADEC(num_nodes, base_capacity)
print("Inizio simulazione avanzata...\n")
for _ in range(max_steps):
system.step(base_inflow, regen_rate)
print("\nSimulazione terminata.")
system.plot()
"""
# Visualizzazione 3D di Enantiomeri (Basata su Python -
Un visualizzatore 3D stabile e interattivo degli enantiomeri. Potrai selezionare diverse coppie di enantiomeri e le loro forme R/S, e visualizzarle con interattività manuale.
---
### Come Funziona:
1. **Installazione:** La prima riga installerà automaticamente le librerie necessarie (`py3Dmol` e `rdkit`).
2. **Selezione:** Utilizza il menu a discesa per scegliere una coppia di enantiomeri e i pulsanti radio per selezionare la specifica forma (R o S).
3. **Visualizzazione 3D Interattiva:** Il modello 3D della molecola selezionata apparirà nell'area sottostante. Dovrebbe rimanere visibile e interattivo mentre cambi le selezioni.
4. **Interattività Manuale:** Puoi trascinare il modello 3D con il mouse per ruotarlo in qualsiasi direzione, o usare la rotellina per zoomare. La rotazione è completamente sotto il tuo controllo.
5. **Descrizione:** Una breve descrizione della molecola selezionata apparirà sotto il visualizzatore.
6. **Legenda Colori:** Una legenda chiara spiegherà il significato dei colori degli atomi.
---
"""
# Cella di Codice: Setup delle librerie e Definizione Molecole
# Esegui questa cella per installare py3Dmol e rdkit
get_ipython().system('pip install py3Dmol rdkit')
import py3Dmol
from IPython.display import display, HTML
import ipywidgets as widgets
from ipywidgets import Layout, Output
from rdkit import Chem
from rdkit.Chem import AllChem
# Definizione delle molecole e delle loro proprietà
# Le SMILES ora includono la notazione stereochimica @ o @@
# per differenziare R e S in modo che RDKit possa costruire modelli 3D distinti.
enantiomer_info = {
'Carvone': {
'smiles_r': "CC(=O)C1CCC(C(=C)C)[C@@H]1", # R-(-)-Carvone
'smiles_s': "CC(=O)C1CCC(C(=C)C)[C@H]1", # S-(+)-Carvone
'description': 'Il Carvone è noto per i suoi odori distinti: menta per la forma R e cumino per la forma S.'
},
'Limonene': {
'smiles_r': "CC1=CCC(C(C)=C)[C@@H]1", # R-(+)-Limonene
'smiles_s': "CC1=CCC(C(C)=C)[C@H]1", # S-(-)-Limonene
'description': 'Il Limonene conferisce l\'odore di arancia alla forma R e di pino alla forma S.'
},
'Acido Lattico': {
'smiles_r': "C[C@@H](O)C(=O)O", # D-(-)-Acido Lattico (configurazione R)
'smiles_s': "C[C@H](O)C(=O)O", # L-(+)-Acido Lattico (configurazione S)
'description': 'L\'Acido Lattico è fondamentale in biologia; la forma L è quella comune nei muscoli.'
},
'Alanina': {
'smiles_r': "C[C@@H](N)C(=O)O", # D-Alanina
'smiles_s': "C[C@H](N)C(=O)O", # L-Alanina
'description': 'L\'Alanina è un amminoacido. La forma L è predominante nelle proteine viventi.'
},
'Ibuprofene': {
'smiles_r': "CC(C)CC1=CC=C(C=C1)[C@@H](C)C(=O)O", # (R)-Ibuprofene
'smiles_s': "CC(C)CC1=CC=C(C=C1)[C@H](C)C(=O)O", # (S)-Ibuprofene
'description': 'Solo la forma S dell\'Ibuprofene è l\'agente antinfiammatorio attivo.'
},
'Talidomide': {
'smiles_r': "O=C1NC(=O)[C@@H](C2=CC=CC=C2C(=O)N3CCC3=O)C1", # (R)-Talidomide
'smiles_s': "O=C1NC(=O)[C@H](C2=CC=CC=C2C(=O)N3CCC3=O)C1", # (S)-Talidomide
'description': 'La Talidomide è un esempio cruciale: la forma R è sedativa, la forma S è teratogena.'
},
'Metanfetamina': {
'smiles_r': "CN[C@@H](C)CC1=CC=CC=C1", # D-Metanfetamina
'smiles_s': "CN[C@H](C)CC1=CC=CC=C1", # L-Metanfetamina
'description': 'La D-Metanfetamina è uno stimolante; la L-Metanfetamina è un decongestionante.'
},
'Propanololo': {
'smiles_r': "CC(C)NC[C@@H](O)COC1=CC=CC=C1", # (R)-Propanololo
'smiles_s': "CC(C)NC[C@H](O)COC1=CC=CC=C1", # (S)-Propanololo
'description': 'La forma S del Propanololo è l\'isomero attivo come beta-bloccante.'
},
'Aspartame': {
'smiles_r': "NC(CC(=O)OC)C(=O)N[C@@H](CC1=CC=CC=C1)C(=O)O", # L,L-Aspartame
'smiles_s': "NC(CC(=O)OC)C(=O)N[C@H](CC1=CC=CC=C1)C(=O)O", # L,D-Aspartame
'description': 'Solo un particolare stereoisomero dell\'Aspartame è dolce.'
},
'Nicotina': {
'smiles_r': "CN1CCC[C@@H]1C2=CN=CC=C2", # (R)-Nicotina
'smiles_s': "CN1CCC[C@H]1C2=CN=CC=C2", # (S)-Nicotina
'description': 'La Nicotina naturalmente presente è la forma S, che è più attiva della forma R.'
}
}
# Area di output dedicata alla visualizzazione 3D e alla descrizione
viewer_output_area = Output(layout=Layout(border='1px solid #ccc', border_radius='10px',
width='600px', height='450px', background_color='#f0f0f0'))
description_output_area = Output(layout=Layout(margin_top='20px', width='600px'))
# Cella di Codice: Funzione per aggiornare il visualizzatore 3D e la descrizione
def update_enantiomer_3d(enantiomer_pair_key, enantiomer_form):
data = enantiomer_info[enantiomer_pair_key]
current_smiles = data['smiles_r'] if enantiomer_form == 'R' else data['smiles_s']
current_description = data['description']
# Generazione del modello 3D con RDKit per garantire la stereochimica
mol = Chem.MolFromSmiles(current_smiles)
mol_block = ""
if mol is not None:
mol = Chem.AddHs(mol)
AllChem.EmbedMolecule(mol, AllChem.ETKDG()) # Genera conformazione 3D
AllChem.MMFFOptimizeMolecule(mol) # Ottimizza la geometria
mol_block = Chem.MolToMolBlock(mol) # Converte in formato molblock per py3Dmol
else:
print(f"Errore: Impossibile generare molecola da SMILES: {current_smiles}. Controlla la stringa SMILES stereochimica.")
mol_block = "Failed to generate molecule." # Messaggio di errore visibile
# Aggiorna l'area di output del viewer
with viewer_output_area:
viewer_output_area.clear_output(wait=True) # Pulisce il contenuto precedente
if mol_block != "Failed to generate molecule.":
view = py3Dmol.view(width=600, height=450) # Crea un nuovo viewer ogni volta
view.addModel(mol_block, 'mol')
view.setStyle({'stick': {'radius': 0.25}, 'sphere': {'radius': 0.3}})
view.zoomTo()
view.show() # Usa show() per visualizzare il viewer nel widget Output
else:
display(HTML("<p style='color:red; text-align:center; font-size:1.2em;'>Errore nella generazione del modello molecolare.</p>"))
# Aggiorna l'area di output della descrizione
with description_output_area:
description_output_area.clear_output(wait=True) # Pulisce il contenuto precedente
description_html = f"""
<div style="background-color: #f0f8ff; border-radius: 10px; padding: 15px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1); color: #004d40; font-size: 1.1em;">
<p style="margin: 0;">{current_description}</p>
</div>
"""
display(HTML(description_html))
# Cella di Codice: Widget interattivi e logica di aggiornamento
enantiomer_pair_selector = widgets.Dropdown(
options=list(enantiomer_info.keys()),
description='Seleziona Molecola:',
disabled=False,
layout=Layout(width='auto', margin='10px 0')
)
enantiomer_form_selector = widgets.RadioButtons(
options=['R', 'S'],
description='Forma:',
disabled=False,
layout=Layout(width='auto', display='flex', justify_content='center', margin='10px 0')
)
# Aggiunta della legenda dei colori
color_legend_html = """
<div style="margin-top: 30px; padding: 15px; background-color: #f7f7e0; border-radius: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h3 style="color: #6a0080; text-align: center; margin-bottom: 15px;">Legenda Colori Atomi (Convenzione CPK)</h3>
<div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 20px;">
<div style="display: flex; align-items: center;">
<span style="display: inline-block; width: 20px; height: 20px; border-radius: 50%; background-color: #A0A0A0; border: 1px solid #777; margin-right: 8px;"></span>
<strong>Carbonio (C)</strong>
</div>
<div style="display: flex; align-items: center;">
<span style="display: inline-block; width: 20px; height: 20px; border-radius: 50%; background-color: #FF0D0D; border: 1px solid #C00; margin-right: 8px;"></span>
<strong>Ossigeno (O)</strong>
</div>
<div style="display: flex; align-items: center;">
<span style="display: inline-block; width: 20px; height: 20px; border-radius: 50%; background-color: #3333FF; border: 1px solid #0000C0; margin-right: 8px;"></span>
<strong>Azoto (N)</strong>
</div>
<div style="display: flex; align-items: center;">
<span style="display: inline-block; width: 20px; height: 20px; border-radius: 50%; background-color: #FFFFFF; border: 1px solid #CCC; margin-right: 8px;"></span>
<strong>Idrogeno (H)</strong>
</div>
</div>
<p style="text-align: center; font-size: 0.9em; color: #555; margin-top: 15px;">
Altri atomi (es. Cl, S, P, Br, I) avrebbero colori specifici se presenti.
</p>
</div>
"""
# Funzione per gestire i cambiamenti dei widget
def on_widget_change(change):
update_enantiomer_3d(enantiomer_pair_selector.value, enantiomer_form_selector.value)
# Collega la funzione ai cambiamenti dei widget
enantiomer_pair_selector.observe(on_widget_change)
enantiomer_form_selector.observe(on_widget_change)
# Visualizza i controlli (dropdown e radio buttons)
display(HTML("""
<style>
.controls-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
margin-bottom: 30px;
background-color: #e0f2f7;
padding: 20px;
border-radius: 15px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.control-group {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.radio-group {
display: flex;
gap: 15px;
}
</style>
""")) # Stile CSS per i controlli
display(widgets.HBox([enantiomer_pair_selector, enantiomer_form_selector],
layout=Layout(justify_content='center', align_items='flex-start')))
# Visualizza la legenda dei colori
display(HTML(color_legend_html))
# Organizza le aree di output del viewer e della descrizione in un contenitore
viewer_and_description_container = widgets.VBox([viewer_output_area, description_output_area],
layout=Layout(align_items='center', margin='20px auto'))
display(viewer_and_description_container)
# Esegui la visualizzazione iniziale al caricamento
update_enantiomer_3d(enantiomer_pair_selector.value, enantiomer_form_selector.value)
--- Simulatore di Figure di Chladni (Approssimato) per Numeri Primi ---
Questo script genera visualizzazioni 2D di pattern simili alle figure di Chladni.
Questi pattern sono il risultato dell'interazione di due 'modi' di vibrazione (m, n).
Le **zone più scure** rappresentano le **linee nodali** (punti di ampiezza zero).
Puoi inserire coppie di numeri interi (preferibilmente primi) per m e n.
--- IMPORTANTE: Limiti della Simulazione ---
Questa è una **SIMULAZIONE MATEMATICA BASATA SU MODELLI DI ONDE STAZIONARIE**.
NON è una simulazione fisica completa della cimatica
(che richiede calcoli FEM e considera proprietà del materiale, smorzamento, ecc.).
Tuttavia, genera pattern visivamente molto simili a quelli reali delle piastre vibranti.
import numpy as np
import matplotlib.pyplot as plt
# --- Funzione per verificare se un numero è primo ---
def is_prime(n):
if n < 2:
return False
for i in range(2, int(np.sqrt(n)) + 1):
if n % i == 0:
return False
return True
# --- Funzione per generare un pattern in stile Chladni ---
def generate_chladni_pattern(m, n, L=1.0):
# m e n sono i "modi" di vibrazione (numero di nodi in X e Y)
# L è la lunghezza del lato della piastra (per normalizzazione)
x = np.linspace(0, L, 600) # Da 0 a L
y = np.linspace(0, L, 600) # Da 0 a L
X, Y = np.meshgrid(x, y)
# Formula semplificata per i modi di vibrazione di una piastra quadrata
# Questa è una delle forme base dei modi nodali.
# I termini aggiuntivi (come np.cosh o np.cos) e le costanti dipendono
# dalle condizioni al contorno (es. bordi liberi o fissi).
# Questa versione si concentra sulla combinazione armonica per formare nodi.
# Per una piastra con bordi semplicemente supportati (o equivalentemente, un'onda stazionaria)
# si usa tipicamente il prodotto di due seni.
# Tuttavia, per le figure di Chladni più complesse, si considerano combinazioni lineari
# di modi armonici. Proviamo una formula che genera pattern riconoscibili.
# Questa formula è un'approssimazione che spesso genera pattern simili
# a quelli di Chladni, specialmente per valori specifici di m e n.
# Combina funzioni seno e coseno per creare intersezioni nodali.
# Un modo classico per i modi di una piastra quadrata è:
# Z = A * cos(m*pi*x/L) * cos(n*pi*y/L) + B * cos(n*pi*x/L) * cos(m*pi*y/L)
# Le costanti A e B e le relazioni tra m e n dipendono dalla frequenza di risonanza.
# Per semplicità e per mostrare pattern distintivi per m, n:
# Proviamo una combinazione che produce pattern interessanti:
Z = np.sin(m * np.pi * X / L) * np.sin(n * np.pi * Y / L) + \
np.sin(n * np.pi * X / L) * np.sin(m * np.pi * Y / L)
# Per visualizzare le linee nodali, dove l'ampiezza è zero, usiamo np.abs(Z)
# e una colormap che evidenzi i valori bassi (neri o scuri).
fig, ax = plt.subplots(figsize=(7, 7))
im = ax.imshow(np.abs(Z), cmap='plasma', origin='lower', extent=[0, L, 0, L])
# Titolo che mostra i modi (m, n) usati
ax.set_title(f'Figura di Chladni (approssimata) per (m, n) = ({m}, {n})', fontsize=16)
ax.axis('off')
cbar = plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
cbar.set_label('Ampiezza (Valore Assoluto)', fontsize=12)
plt.tight_layout()
return fig
# --- Istruzioni e disclaimer per l'utente ---
print("--- Simulatore di Figure di Chladni (Approssimato) per Numeri Primi ---")
print("Questo script genera visualizzazioni 2D di pattern simili alle figure di Chladni.")
print("Questi pattern sono il risultato dell'interazione di due 'modi' di vibrazione (m, n).")
print("Le **zone più scure** rappresentano le **linee nodali** (punti di ampiezza zero).")
print("Puoi inserire coppie di numeri interi (preferibilmente primi) per m e n.")
print("\n")
print("--- IMPORTANTE: Limiti della Simulazione ---")
print("Questa è una **SIMULAZIONE MATEMATICA BASATA SU MODELLI DI ONDE STAZIONARIE**.")
print("NON è una simulazione fisica completa della cimatica")
print("(che richiede calcoli FEM e considera proprietà del materiale, smorzamento, ecc.). ")
print("Tuttavia, genera pattern visivamente molto simili a quelli reali delle piastre vibranti.")
print("---")
# --- Input per le coppie di modi (m, n) ---
print("\nInserisci coppie di numeri interi (m,n), separati da virgole, e le coppie")
print("stesse separate da punto e virgola ';'. Prova con numeri primi per m e n.")
print("Esempio: '2,3; 3,5; 2,5; 4,7'")
input_pairs_str = input("Coppie suggerite (m,n): ")
pairs_to_test = []
try:
for pair_str in input_pairs_str.split(';'):
parts = pair_str.strip().split(',')
if len(parts) == 2:
val1 = int(parts[0].strip())
val2 = int(parts[1].strip())
# I modi m e n devono essere numeri positivi
if val1 >= 0 and val2 >= 0:
pairs_to_test.append((val1, val2))
else:
print(f"Skippato la coppia ({val1},{val2}): m e n devono essere interi non negativi.")
else:
print(f"Formato coppia non valido: '{pair_str.strip()}'. Skippato.")
except ValueError:
print("\nInput non valido. Assicurati di inserire coppie di numeri interi (es: 1,2).")
print("Verranno usati esempi predefiniti per dimostrazione.")
pairs_to_test = [] # Reset
if not pairs_to_test:
print("\nNessuna coppia valida inserita. Verranno usati esempi predefiniti:")
print("[(0,0), (1,0), (0,1), (1,1), (1,2), (2,1), (2,2), (2,3), (3,2), (3,3), (3,5), (5,3)]")
pairs_to_test = [(0,0), (1,0), (0,1), (1,1), (1,2), (2,1), (2,2), (2,3), (3,2), (3,3), (3,5), (5,3)]
# --- Generazione e visualizzazione dei pattern ---
print("\n--- Generazione dei pattern in corso... ---")
# Ordina e rimuove i duplicati per una visualizzazione più pulita
sorted_unique_pairs = sorted(list(set(pairs_to_test)))
for m, n in sorted_unique_pairs:
print(f"Creando pattern per (m, n) = ({m}, {n})...")
generate_chladni_pattern(m, n)
plt.show()
print("\n--- Fine della simulazione ---")
Il programma calcola i primoriali (il prodotto dei primi k numeri primi) e poi verifica se la sottrazione di un altro numero primo da questi primoriali produce a sua volta un numero primo. In pratica, si cerca di scoprire se sottrarre un numero primo successivo a quello che ha generato il primoriale possa dare come risultato un altro numero primo.
import math
from sympy import isprime # Assicurati di avere sympy installato: pip install sympy
# Funzione ausiliaria per testare la primalità di numeri piccoli (usata per trovare i primi iniziali)
def _is_prime_small(num):
if num < 2:
return False
for i in range(2, int(math.sqrt(num)) + 1):
if num % i == 0:
return False
return True
def get_primes_up_to_index(count):
primes = []
num = 2
while len(primes) < count:
if _is_prime_small(num):
primes.append(num)
num += 1
return primes
def get_nth_prime(n):
if n <= 0:
raise ValueError("L'indice del primo deve essere positivo.")
count = 0
num = 2
while True:
if _is_prime_small(num):
count += 1
if count == n:
return num
num += 1
def calculate_primorial_and_last_prime(p_index):
primes = get_primes_up_to_index(p_index)
primorial = 1
for p in primes:
primorial *= p
return primorial, primes[-1]
# --- Funzione principale per l'interazione ---
def main():
print("-" * 70)
while True:
try:
num_primorials_to_test = int(input("\nQuanti primoriali (P_i#) vuoi testare? (Es. 5 per P_1# fino a P_5#): "))
if num_primorials_to_test <= 0:
print("Inserisci un numero intero positivo.")
continue
break
except ValueError:
print("Input non valido. Inserisci un numero intero.")
while True:
try:
max_n_offset_attempts = int(input("Quanti tentativi (valori di 'n' in P(i+n)) vuoi eseguire per ogni primoriale? (Es. 100): "))
if max_n_offset_attempts <= 0:
print("Inserisci un numero intero positivo.")
continue
break
except ValueError:
print("Input non valido. Inserisci un numero intero.")
print(f"\nInizio la ricerca per i primi {num_primorials_to_test} primoriali, con un massimo di {max_n_offset_attempts} tentativi di 'n' per ciascuno...")
print("=" * 70) # Usiamo un separatore diverso per l'inizio dei risultati
# Rimosse completamente le intestazioni di colonna
# print(f"{'Pk (indice)':<15} | {'Pk (valore)':<15} | {'Primoriale Pk#':<25} | {'P(k+n) sottratto':<20} | {'Risultato (Primo)':<25} | {'Indice di n':<15}")
# print("-" * 15 + " | " + "-" * 15 + " | " + "-" * 25 + " | " + "-" * 20 + " | " + "-" * 25 + " | " + "-" * 15)
for i in range(1, num_primorials_to_test + 1):
print(f"\n--- Analisi per Primoriale P_{i}# ---")
try:
current_primorial, p_k_value = calculate_primorial_and_last_prime(i)
print(f" i (indice): {i}")
print(f" P_i (valore): {p_k_value}")
print(f" Primoriale P_{i}#: {current_primorial}")
print(" Risultati:")
print(" " + "-" * 30)
found_any_prime_difference = False
for n_offset in range(1, max_n_offset_attempts + 1):
prime_to_subtract_index = i + n_offset
subtracted_prime_value = get_nth_prime(prime_to_subtract_index)
if subtracted_prime_value >= current_primorial:
# Se non è stato trovato alcun primo fino a questo punto e P(k+n) è già troppo grande,
# stampiamo un messaggio specifico per questo "fallimento"
if not found_any_prime_difference: # E' il primo caso non trovato
print(f" Nessun numero primo valido trovato per n={n_offset} (P(i+n) = {subtracted_prime_value} è troppo grande o uguale al primoriale).")
# Non impostiamo found_any_prime_difference a True qui perché non è un primo trovato.
break # Interrompi la ricerca per n_offset per questo primoriale
# Continueremo a stampare "Nessun primo trovato dopo X tentativi" sotto se necessario.
result = current_primorial - subtracted_prime_value
if isprime(result):
found_any_prime_difference = True
print(f" - Per n={n_offset}: P(i+n) sottratto = {subtracted_prime_value}, Risultato = {result} (È PRIMO!)")
# else:
# Se non è primo e vogliamo comunque mostrare i tentativi non primi (ma l'utente ha detto "non colonne", suggerendo sintesi)
# Potremmo aggiungere:
# print(f" - Per n={n_offset}: P(k+n) sottratto = {subtracted_prime_value}, Risultato = {result} (Non è primo)")
# Per ora, in linea con "non colonne" e l'output sintetico, mostriamo solo i primi trovati.
if not found_any_prime_difference:
print(f" Nessun numero primo trovato nella forma P_i# - P(i+n) entro {max_n_offset_attempts} tentativi.")
print("=" * 70) # Separatore tra i primoriali
except ValueError as ve:
print(f"Errore per P_{i}#: {ve}")
print("Saltando al prossimo primoriale...")
print("=" * 70)
except Exception as e:
print(f"Si è verificato un errore inatteso durante l'elaborazione del primoriale {i}: {e}")
print("Saltando al prossimo primoriale...")
print("=" * 70)
print("\nCalcoli completati!")
# Esegui la funzione principale quando lo script viene eseguito
if __name__ == "__main__":
main()
class MatriceCiclica:
def __init__(self, righe, valore_iniziale):
self.righe = righe
self.matrice = [[0] for _ in range(righe)]
self.posizione = 0
self.valore = valore_iniziale
# Inizializza con valore nella prima riga, come da regola
self.matrice[0][0] = valore_iniziale
def aggiorna(self, nuovo_valore):
self.valore = nuovo_valore
# Sposta il valore di una riga in basso ciclicamente: posizione = (posizione + 1) mod righe
self.posizione = (self.posizione + 1) % self.righe
# Azzeramento e inserimento del nuovo valore
# (Il nuovo valore è sempre il contatore globale, ma l'azione di azzerare/spostare è la chiave)
for i in range(self.righe):
self.matrice[i][0] = 0
self.matrice[self.posizione][0] = self.valore
def prima_riga_valore(self):
# Ritorna il valore (0 o il contatore) nella riga 0
return self.matrice[0][0]
def get_righe(self):
return self.righe
def get_matrice(self):
return self.matrice
class ContatoreMatriceMultipla:
def __init__(self):
self.contatore = 2
self.matrici = []
# Inizializzazione: solo la prima matrice di dimensione 2
# Il contatore parte da 2 e viene subito inserito nella matrice iniziale.
prima = MatriceCiclica(2, self.contatore)
self.matrici.append(prima)
# NOTA: non serve più 'self.seconda_matrice_attiva' o logiche speciali.
def step(self):
# 1. Incrementa il contatore globale
self.contatore += 1
nuovo_valore = self.contatore
# 2. Aggiorna *tutte* le matrici esistenti con la logica ciclica universale
# La matrice d=2 ora usa il metodo 'aggiorna', che simula correttamente la parità.
# - A C=3 (passo 1), d=2: (0+1)%2 = pos 1. M[0]=0. (Generazione di d=3)
# - A C=4 (passo 2), d=2: (1+1)%2 = pos 0. M[0]=4.
# - A C=5 (passo 3), d=2: (0+1)%2 = pos 1. M[0]=0. (Generazione di d=5)
for matrice in self.matrici:
matrice.aggiorna(nuovo_valore)
# 3. Controlla la condizione di generazione (UNIVERSALE)
# Condizione: prima riga di tutte le matrici = 0
if all(m.prima_riga_valore() == 0 for m in self.matrici):
# Se la condizione è soddisfatta, il contatore attuale è il nuovo numero primo (o l'1 che lo precede nel crivello)
# Aggiungi nuova matrice con righe = valore contatore e valore iniziale = contatore
nuova = MatriceCiclica(self.contatore, self.contatore)
self.matrici.append(nuova)
# Le funzioni di stampa e il blocco main rimangono invariati per la loro correttezza.
def stampa_stato_affiancato(self):
print(f"Contatore: {self.contatore}")
larghezza_colonna = 20 # larghezza fissa per colonna
# Intestazioni centrate
intestazioni = []
for i, m in enumerate(self.matrici, start=1):
intestazioni.append(f"Matrice {i} ({m.get_righe()} righe)".center(larghezza_colonna))
print("".join(intestazioni))
# Numero massimo di righe tra matrici
max_righe = max(m.get_righe() for m in self.matrici)
# Stampa righe affiancate con allineamento
for riga in range(max_righe):
righe_stampate = []
for m in self.matrici:
if riga < m.get_righe():
val = m.get_matrice()[riga][0]
# Evidenzia la prima riga in rosso se contiene un valore (non zero)
if riga == 0 and val != 0:
righe_stampate.append(f"\033[91m{val:^{larghezza_colonna}}\033[0m") # Rosso
else:
righe_stampate.append(f"{val:^{larghezza_colonna}}")
else:
righe_stampate.append(" " * larghezza_colonna)
print("".join(righe_stampate))
print("-" * (larghezza_colonna * len(self.matrici)))
def main():
while True:
try:
num_steps = int(input("Inserisci il numero di step da eseguire: "))
if num_steps >= 0:
break
else:
print("Errore: il numero di step deve essere non negativo.")
except ValueError:
print("Errore: devi inserire un numero intero valido.")
cm = ContatoreMatriceMultipla()
cm.stampa_stato_affiancato()
for _ in range(num_steps):
cm.step()
cm.stampa_stato_affiancato()
if __name__ == "__main__":
main()
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.cm as cm # Importa la mappa di colore per i colori dei punti
def primi_fino_a_n(n):
"""
Genera i primi 'n' numeri primi.
Questa funzione implementa un algoritmo basilare per trovare i numeri primi.
"""
primi = []
num = 2
while len(primi) < n:
is_prime = True
# Controlla solo i divisori fino alla radice quadrata del numero corrente
# Questa è un'ottimizzazione per rendere il calcolo più efficiente.
for p_test in primi:
if p_test * p_test > num: # Se il potenziale divisore supera la radice, smettiamo di controllare
break
if num % p_test == 0: # Se il numero è divisibile, non è primo
is_prime = False
break
if is_prime:
primi.append(num) # Aggiunge il numero alla lista dei primi
num += 1 # Passa al numero successivo
return primi
def disegna_sfera_olografica(num_elementi):
"""
Disegna una rappresentazione sferica dei primi 'num_elementi' numeri primi.
I numeri primi sono distribuiti sulla superficie della sfera usando la spirale di Fibonacci.
Viene mostrata anche una proiezione 2D per evidenziare il pattern a spirale,
e una legenda dei colori spiega il significato delle tonalità.
"""
primi = primi_fino_a_n(num_elementi)
if not primi:
print("Attenzione: Nessun numero primo generato per il numero di elementi richiesto.")
return
# Trova il numero primo più grande per normalizzare i colori e le dimensioni
max_primo = max(primi) if primi else 1
raggio = 1.0 # Definisce il raggio base della sfera per la visualizzazione
# Configura la figura con due subplot (due grafici affiancati)
fig = plt.figure(figsize=(16, 8)) # La larghezza è maggiore per accomodare due grafici
# --- Subplot 1: Visualizzazione 3D della Sfera ---
ax3d = fig.add_subplot(121, projection='3d') # Primo subplot: 1 riga, 2 colonne
# Liste per memorizzare le coordinate, i colori e le dimensioni dei punti
punti_sfera_x = []
punti_sfera_y = []
punti_sfera_z = []
colori = []
dimensioni = []
# La costante dell'angolo aureo, fondamentale per la spirale di Fibonacci
golden_angle = np.pi * (3 - np.sqrt(5))
# Cicla attraverso i numeri primi per calcolare la loro posizione sulla sfera
for i, p in enumerate(primi):
# Calcola le coordinate sferiche per la spirale di Fibonacci
# 'y' varia da 1 a -1, distribuendo i punti verticalmente sulla sfera
y = 1 - (i / float(num_elementi - 1)) * 2
# Calcola il raggio del cerchio orizzontale a questa specifica altezza 'y'
radius_at_y = np.sqrt(1 - y * y)
# Calcola l'angolo (theta) per la disposizione a spirale attorno all'asse y
theta = golden_angle * i
# Converte le coordinate sferiche (raggio, y, theta) in coordinate cartesiane (x, y, z)
x = np.cos(theta) * radius_at_y * raggio
z = np.sin(theta) * radius_at_y * raggio
# Determina il colore del punto usando la mappa 'viridis'
# I numeri primi più piccoli avranno colori come giallo/verde chiaro; i più grandi blu/viola scuro.
colore_primo = cm.viridis(p / (max_primo + 1))
# Determina la dimensione del punto: i numeri primi più grandi avranno pallini più grandi.
dimensione_primo = 20 + (p / (max_primo + 1)) * 100
# Aggiunge le coordinate, il colore e la dimensione alle rispettive liste
punti_sfera_x.append(x)
punti_sfera_y.append(y * raggio) # Moltiplica anche y per il raggio per coerenza
punti_sfera_z.append(z)
colori.append(colore_primo)
dimensioni.append(dimensione_primo)
# Disegna i punti sulla superficie sferica
# 'edgecolors' e 'linewidths' aggiungono un bordo sottile per una migliore definizione dei punti.
scatter = ax3d.scatter(punti_sfera_x, punti_sfera_y, punti_sfera_z,
c=colori, s=dimensioni, alpha=0.8,
edgecolors='black', linewidths=0.2)
# Aggiunge una linea che collega i punti in ordine di generazione, mostrando il "percorso" della spirale
if len(punti_sfera_x) > 1:
ax3d.plot(punti_sfera_x, punti_sfera_y, punti_sfera_z,
color='darkgray', linestyle='-', linewidth=1.0, alpha=0.7)
# Disegna una wireframe (griglia) trasparente per definire la forma della sfera
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
x_sfera = raggio * np.outer(np.cos(u), np.sin(v))
y_sfera = raggio * np.outer(np.sin(u), np.sin(v))
z_sfera = raggio * np.outer(np.ones(np.size(u)), np.cos(v))
ax3d.plot_wireframe(x_sfera, y_sfera, z_sfera,
color='lightgray', alpha=0.2, linestyle='--', linewidth=0.5)
# Imposta titolo ed etichette degli assi per il grafico 3D
ax3d.set_title(f'Sfera Olografica 3D con {num_elementi} Primi')
ax3d.set_xlabel('Asse X')
ax3d.set_ylabel('Asse Y')
ax3d.set_zlabel('Asse Z')
ax3d.set_aspect('equal') # Assicura che la sfera non appaia distorta
# Imposta i limiti degli assi per una visualizzazione ottimale
limite = raggio * 1.2
ax3d.set_xlim([-limite, limite])
ax3d.set_ylim([-limite, limite])
ax3d.set_zlim([-limite, limite])
# Aggiunge la colorbar come legenda.
# Normalizza i valori per la colorbar in base al range dei numeri primi.
norm = plt.Normalize(vmin=min(primi), vmax=max_primo)
cbar = fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cm.viridis), ax=ax3d, shrink=0.7, pad=0.05)
cbar.set_label('Valore del Numero Primo')
# --- Subplot 2: Proiezione 2D per evidenziare la Spirale ---
ax2d = fig.add_subplot(122) # Secondo subplot: 1 riga, 2 colonne
# Proietta i punti sul piano XZ per mostrare la spirale in 2D
ax2d.scatter(punti_sfera_x, punti_sfera_z, c=colori, s=dimensioni, alpha=0.8,
edgecolors='black', linewidths=0.2)
ax2d.set_title('Proiezione 2D (Piano XZ della Spirale)')
ax2d.set_xlabel('Asse X')
ax2d.set_ylabel('Asse Z')
ax2d.set_aspect('equal') # Mantiene le proporzioni
# Aggiunge la linea anche nella proiezione 2D per rendere il pattern della spirale più evidente
ax2d.plot(punti_sfera_x, punti_sfera_z, color='darkgray', linestyle='-', linewidth=0.5, alpha=0.6)
# Regola lo spazio tra i subplot per evitare sovrapposizioni di etichette e titoli
plt.tight_layout()
plt.show() # Mostra entrambi i grafici
# --- Esecuzione del Programma ---
if __name__ == "__main__":
try:
# Questo è il box di input che funzionerà nel tuo terminale/IDE
num_input = int(input("Inserisci il numero di elementi primi da visualizzare sulla sfera: "))
disegna_sfera_olografica(num_input)
except ValueError:
print("Input non valido. Inserisci un numero intero.")
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
def primi_fino_a_n(n):
primi = []
num = 2
while len(primi) < n:
if all(num % p != 0 for p in primi):
primi.append(num)
num += 1
return primi
def disegna_struttura_semplificata(num_matrici):
primi = primi_fino_a_n(num_matrici)
max_righe = max(primi)
scale = 0.15
raggio = max_righe * scale
angoli = np.linspace(90, 90-360, num_matrici, endpoint=False)
fig, ax = plt.subplots(figsize=(10,10))
ax.set_aspect('equal')
cerchio = plt.Circle((0,0), raggio, color='lightgray', fill=False, linestyle='--')
ax.add_artist(cerchio)
larghezza = 0.5 # larghezza rettangolo fisso
for i, p in enumerate(primi):
angolo = angoli[i]
altezza = p * scale
distanza = raggio - altezza
x = distanza * np.cos(np.radians(angolo))
y = distanza * np.sin(np.radians(angolo))
colore = plt.cm.viridis(p / (max_righe+1))
rect = Rectangle((x - larghezza/2, y), larghezza, altezza, facecolor=colore, edgecolor='black', linewidth=0.5)
t = plt.matplotlib.transforms.Affine2D().rotate_deg_around(x, y, angolo-90) + ax.transData
rect.set_transform(t)
ax.add_patch(rect)
# Posizione etichetta fuori dalla circonferenza, leggermente più esterna
label_dist = raggio + 0.5 # distanza dal centro per etichetta
lx = label_dist * np.cos(np.radians(angolo))
ly = label_dist * np.sin(np.radians(angolo))
ax.text(lx, ly, str(p),
ha='center', va='center',
fontsize=7,
rotation=angolo-90,
rotation_mode='anchor',
bbox=dict(facecolor='white', edgecolor='none', alpha=0.7, pad=1))
ax.set_xlim(-raggio-5, raggio+5)
ax.set_ylim(-raggio-5, raggio+5)
ax.axis('off')
plt.title(f'Struttura semplificata con {num_matrici} matrici prime')
plt.show()
if __name__ == "__main__":
num_matrici = int(input("Inserisci il numero di matrici prime da visualizzare: "))
disegna_struttura_semplificata(num_matrici)
from decimal import Decimal, getcontext
from sympy import primerange, isprime
import math
# Imposta la precisione decimale
getcontext().prec = 50
def primorial(primes):
product = 1
for p in primes:
product *= p
return product
def find_best_prime_approx(target, b, constant_name, constant_value, search_range=5000):
target_int = int(target.to_integral_value(rounding='ROUND_HALF_EVEN'))
best_prime = None
best_error = None
start = max(2, target_int - search_range)
end = target_int + search_range
for candidate in range(start, end + 1):
if isprime(candidate):
a = Decimal(candidate)
ratio = a / b
error = abs(ratio - constant_value)
if (best_error is None) or (error < best_error):
best_error = error
best_prime = a
return {
'constant': constant_name,
'prime_a': best_prime,
'ratio': best_prime / b,
'error': best_error
}
def approx_prime_index(p):
"""
Approssima l'indice del primo p (π(p)) usando il teorema dei numeri primi:
π(p) ~ p / ln(p)
"""
try:
p_float = float(p)
if p_float <= 1:
return 0
return int(p_float / math.log(p_float))
except OverflowError:
p_dec = Decimal(p)
if p_dec <= 1:
return 0
ln_p = p_dec.ln()
return int(p_dec / ln_p)
def main():
prime_start = 2
prime_end = 750
primes_list = list(primerange(prime_start, prime_end + 1))
b_int = primorial(primes_list)
b = Decimal(b_int)
# Costanti fondamentali ad alta precisione
pi = Decimal("3.14159265358979323846264338327950288419716939937510")
e = Decimal("2.71828182845904523536028747135266249775724709369995")
sqrt5 = Decimal(5).sqrt()
phi = (Decimal(1) + sqrt5) / Decimal(2)
gamma = Decimal("0.57721566490153286060651209008240243104215933593992") # Costante di Eulero-Mascheroni
sqrt2 = Decimal(2).sqrt()
brun = Decimal("1.902160583104") # Costante di Brun (approssimazione)
catalan = Decimal("0.915965594177219015054603514") # Costante di Catalan (approssimazione)
gauss_kuzmin_wirsing = Decimal("0.303663002898732658") # Costante di Gauss-Kuzmin-Wirsing
constants = [
('pi', pi),
('e', e),
('phi', phi),
('gamma (Euler-Mascheroni)', gamma),
('sqrt(2)', sqrt2),
('Brun', brun),
('Catalan', catalan),
('Gauss-Kuzmin-Wirsing', gauss_kuzmin_wirsing)
]
print(f"Primorial b (prodotto primi da {prime_start} a {prime_end}): {b}\n")
results = []
for name, value in constants:
target = value * b
res = find_best_prime_approx(target, b, name, value)
results.append(res)
for r in results:
print(f"Costante: {r['constant']}")
print(f" Primo a trovato: {r['prime_a']}")
p = int(r['prime_a'])
idx_approx = approx_prime_index(p)
print(f" Indice approssimato del primo a nell’insieme dei primi: {idx_approx:.3e}")
print(f" Rapporto a/b: {r['ratio']}")
print(f" Errore assoluto: {r['error']:.3E}\n")
if __name__ == "__main__":
main()
from decimal import Decimal, getcontext
from sympy import primerange, isprime
import math
# Imposta la precisione decimale
getcontext().prec = 50
def primorial(primes):
product = 1
for p in primes:
product *= p
return product
def find_best_prime_approx(target, b, constant_name, constant_value, primes_list, search_range=5000):
target_int = int(target.to_integral_value(rounding='ROUND_HALF_EVEN'))
best_prime = None
best_error = None
best_index = None
start = max(2, target_int - search_range)
end = target_int + search_range
for candidate in range(start, end + 1):
if isprime(candidate):
a = Decimal(candidate)
ratio = a / b
error = abs(ratio - constant_value)
if (best_error is None) or (error < best_error):
best_error = error
best_prime = a
# Cerca indice nella lista dei primi usati per il primorial
try:
best_index = primes_list.index(candidate) + 1 # indice a partire da 1
except ValueError:
best_index = None
return {
'constant': constant_name,
'prime_a': best_prime,
'prime_index': best_index,
'ratio': best_prime / b,
'error': best_error
}
def approx_prime_index(p):
"""
Approssima l'indice del primo p (π(p)) usando il teorema dei numeri primi:
π(p) ~ p / ln(p)
"""
try:
p_float = float(p)
if p_float <= 1:
return 0
return int(p_float / math.log(p_float))
except OverflowError:
p_dec = Decimal(p)
if p_dec <= 1:
return 0
ln_p = p_dec.ln()
return int(p_dec / ln_p)
def main():
prime_start = 2
prime_end = 650
primes_list = list(primerange(prime_start, prime_end + 1))
b_int = primorial(primes_list)
b = Decimal(b_int)
# Costanti fisiche fondamentali (CODATA 2018, valori approssimati)
constants = [
('Speed of Light (c) [m/s]', Decimal("299792458")), # definita esattamente
('Planck Constant (h) [J·s]', Decimal("6.62607015e-34")), # definita esattamente
('Elementary Charge (e) [C]', Decimal("1.602176634e-19")), # definita esattamente
('Gravitational Constant (G) [m^3·kg^-1·s^-2]', Decimal("6.67430e-11")),
('Avogadro Constant (N_A) [mol^-1]', Decimal("6.02214076e23")), # definita esattamente
('Boltzmann Constant (k) [J/K]', Decimal("1.380649e-23")), # definita esattamente
('Fine-structure Constant (α)', Decimal("7.2973525693e-3")),
('Electron Mass (m_e) [kg]', Decimal("9.10938356e-31")),
('Proton Mass (m_p) [kg]', Decimal("1.672621898e-27")),
('Neutron Mass (m_n) [kg]', Decimal("1.674927471e-27")),
('Vacuum Permittivity (ε_0) [F/m]', Decimal("8.8541878128e-12")),
('Vacuum Permeability (μ_0) [N/A^2]', Decimal("1.25663706212e-6")),
('Stefan-Boltzmann Constant (σ) [W·m^-2·K^-4]', Decimal("5.670374419e-8")),
('Rydberg Constant (R_∞) [m^-1]', Decimal("10973731.568160")),
('Bohr Radius (a_0) [m]', Decimal("5.29177210903e-11")),
]
print(f"Primorial b (prodotto primi da {prime_start} a {prime_end}): {b}\n")
results = []
for name, value in constants:
target = value * b
res = find_best_prime_approx(target, b, name, value, primes_list)
results.append(res)
for r in results:
print(f"Costante: {r['constant']}")
print(f" Primo a trovato: {r['prime_a']}")
p = int(r['prime_a'])
idx_approx = approx_prime_index(p)
print(f" Indice approssimato del primo a nell’insieme dei primi: {idx_approx:.3e}")
if r['prime_index'] is not None:
print(f" Indice del primo a nella lista dei primi da {prime_start} a {prime_end}: {r['prime_index']}")
print(f" Rapporto a/b: {r['ratio']}")
print(f" Errore assoluto: {r['error']:.3E}\n")
if __name__ == "__main__":
main()
from decimal import Decimal, getcontext
from sympy import primerange, isprime
import math
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import curve_fit
# Imposta la precisione decimale
getcontext().prec = 50
def primorial(primes):
product = 1
for p in primes:
product *= p
return Decimal(product)
def find_best_prime_approx(target, b, constant_value, search_range=5000):
target_int = int(target.to_integral_value(rounding='ROUND_HALF_EVEN'))
best_prime = None
best_error = None
start = max(2, target_int - search_range)
end = target_int + search_range
for candidate in range(start, end + 1):
if isprime(candidate):
a = Decimal(candidate)
ratio = a / b
error = abs(ratio - constant_value)
if (best_error is None) or (error < best_error):
best_error = error
best_prime = a
return best_prime, best_error
def approx_prime_index(p):
try:
p_float = float(p)
if p_float <= 1:
return 0
return int(p_float / math.log(p_float))
except OverflowError:
p_dec = Decimal(p)
if p_dec <= 1:
return 0
ln_p = p_dec.ln()
return int(p_dec / ln_p)
def power_law(x, a, b):
return a * np.power(x, b)
def main():
# Costanti fondamentali ad alta precisione
pi = Decimal("3.14159265358979323846264338327950288419716939937510")
e = Decimal("2.71828182845904523536028747135266249775724709369995")
sqrt5 = Decimal(5).sqrt()
phi = (Decimal(1) + sqrt5) / Decimal(2)
gamma = Decimal("0.57721566490153286060651209008240243104215933593992")
sqrt2 = Decimal(2).sqrt()
brun = Decimal("1.902160583104")
catalan = Decimal("0.915965594177219015054603514")
gkw = Decimal("0.303663002898732658")
constants = [
('pi', pi),
('e', e),
('phi', phi),
('gamma', gamma),
('sqrt(2)', sqrt2),
('Brun', brun),
('Catalan', catalan),
('Gauss-Kuzmin-Wirsing', gkw)
]
prime_start = 2
max_prime_counts = [100, 200, 300, 400, 500] # ridotto per velocità
results = {name: {'errors': [], 'orders': []} for name, _ in constants}
for n_primes in max_prime_counts:
primes_list = list(primerange(prime_start, 300))[:n_primes]
b = primorial(primes_list)
print(f"\nPrimorial con {n_primes} primi: b ha {len(str(b))} cifre")
for name, c in constants:
target = b * c
a, error = find_best_prime_approx(target, b, c, search_range=5000)
if a is None:
print(f" Costante {name}: nessun primo trovato nel range.")
continue
idx_approx = approx_prime_index(a)
ratio = a / b
results[name]['errors'].append(float(error))
results[name]['orders'].append(n_primes)
print(f" Costante: {name}")
print(f" Primo a trovato: {a}")
print(f" Indice approssimato primo: {idx_approx:.3e}")
print(f" Rapporto a/b: {ratio}")
print(f" Errore assoluto: {error:.3E}")
# Grafici e fitting con gestione eccezioni
plt.figure(figsize=(14, 10))
for idx, (name, _) in enumerate(constants, 1):
x = np.array(results[name]['orders'])
y = np.array(results[name]['errors'])
if len(x) < 2:
continue
try:
popt, _ = curve_fit(
power_law, x, y,
maxfev=20000,
p0=[1e-10, -1],
bounds=([0, -np.inf], [np.inf, 0])
)
y_fit = power_law(x, *popt)
label_fit = f'Fit potenza: a={popt[0]:.2e}, b={popt[1]:.2f}'
except RuntimeError as e:
print(f"Fit fallito per {name}: {e}")
y_fit = None
label_fit = "Fit fallito"
plt.subplot(3, 3, idx)
plt.loglog(x, y, 'bo', label='Errore misurato')
if y_fit is not None:
plt.loglog(x, y_fit, 'r-', label=label_fit)
else:
plt.text(0.5, 0.5, 'Fit fallito', horizontalalignment='center',
verticalalignment='center', transform=plt.gca().transAxes)
plt.title(f'Errore vs numero primi per {name}')
plt.xlabel('Numero primi nel primorial')
plt.ylabel('Errore assoluto')
plt.legend()
plt.grid(True, which="both", ls="--")
plt.tight_layout()
plt.show()
if __name__ == "__main__":
main()
from decimal import Decimal, getcontext
from sympy import primerange, isprime
import math
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import curve_fit
# Imposta la precisione decimale
getcontext().prec = 50
def primorial(primes):
product = 1
for p in primes:
product *= p
return Decimal(product)
def find_best_prime_approx(target, b, constant_name, constant_value, primes_list, search_range=5000):
target_int = int(target.to_integral_value(rounding='ROUND_HALF_EVEN'))
best_prime = None
best_error = None
best_index = None
start = max(2, target_int - search_range)
end = target_int + search_range
for candidate in range(start, end + 1):
if isprime(candidate):
a = Decimal(candidate)
ratio = a / b
error = abs(ratio - constant_value)
if (best_error is None) or (error < best_error):
best_error = error
best_prime = a
# Cerca indice nella lista dei primi usati per il primorial
try:
best_index = primes_list.index(candidate) + 1 # indice a partire da 1
except ValueError:
best_index = None
return {
'constant': constant_name,
'prime_a': best_prime,
'prime_index': best_index,
'ratio': best_prime / b if best_prime else None,
'error': best_error
}
def approx_prime_index(p):
"""
Approssima l'indice del primo p (π(p)) usando il teorema dei numeri primi:
π(p) ~ p / ln(p)
"""
try:
p_float = float(p)
if p_float <= 1:
return 0
return int(p_float / math.log(p_float))
except OverflowError:
p_dec = Decimal(p)
if p_dec <= 1:
return 0
ln_p = p_dec.ln()
return int(p_dec / ln_p)
def power_law(x, a, b):
return a * np.power(x, b)
def main():
prime_start = 2
prime_limits = [100, 200, 300, 400, 500] # diversi limiti per primorial
# Costanti fisiche fondamentali (CODATA 2018, valori approssimati)
constants = [
('Speed of Light (c) [m/s]', Decimal("299792458")),
('Planck Constant (h) [J·s]', Decimal("6.62607015e-34")),
('Elementary Charge (e) [C]', Decimal("1.602176634e-19")),
('Gravitational Constant (G) [m^3·kg^-1·s^-2]', Decimal("6.67430e-11")),
('Avogadro Constant (N_A) [mol^-1]', Decimal("6.02214076e23")),
('Boltzmann Constant (k) [J/K]', Decimal("1.380649e-23")),
('Fine-structure Constant (α)', Decimal("7.2973525693e-3")),
('Electron Mass (m_e) [kg]', Decimal("9.10938356e-31")),
('Proton Mass (m_p) [kg]', Decimal("1.672621898e-27")),
('Neutron Mass (m_n) [kg]', Decimal("1.674927471e-27")),
('Vacuum Permittivity (ε_0) [F/m]', Decimal("8.8541878128e-12")),
('Vacuum Permeability (μ_0) [N/A^2]', Decimal("1.25663706212e-6")),
('Stefan-Boltzmann Constant (σ) [W·m^-2·K^-4]', Decimal("5.670374419e-8")),
('Rydberg Constant (R_∞) [m^-1]', Decimal("10973731.568160")),
('Bohr Radius (a_0) [m]', Decimal("5.29177210903e-11")),
]
results = {name: {'errors': [], 'orders': []} for name, _ in constants}
for prime_end in prime_limits:
primes_list = list(primerange(prime_start, prime_end + 1))
b = primorial(primes_list)
print(f"\nPrimorial con primi da {prime_start} a {prime_end} (tot {len(primes_list)}): b ha {len(str(b))} cifre")
for name, c in constants:
target = b * c
res = find_best_prime_approx(target, b, name, c, primes_list)
if res['prime_a'] is None:
print(f" Costante {name}: nessun primo trovato nel range.")
continue
idx_approx = approx_prime_index(res['prime_a'])
print(f" Costante: {name}")
print(f" Primo a trovato: {res['prime_a']}")
print(f" Indice approssimato primo: {idx_approx:.3e}")
if res['prime_index'] is not None:
print(f" Indice primo nella lista usata: {res['prime_index']}")
print(f" Rapporto a/b: {res['ratio']}")
print(f" Errore assoluto: {res['error']:.3E}")
results[name]['errors'].append(float(res['error']))
results[name]['orders'].append(len(primes_list))
# Fit e grafici
plt.figure(figsize=(15, 12))
for idx, (name, _) in enumerate(constants, 1):
x = np.array(results[name]['orders'])
y = np.array(results[name]['errors'])
if len(x) < 2:
continue
try:
popt, _ = curve_fit(power_law, x, y, maxfev=20000, p0=[1e-10, -1], bounds=([0, -np.inf], [np.inf, 0]))
y_fit = power_law(x, *popt)
label_fit = f'Fit potenza: a={popt[0]:.2e}, b={popt[1]:.2f}'
except RuntimeError as e:
print(f"Fit fallito per {name}: {e}")
y_fit = None
label_fit = "Fit fallito"
plt.subplot(4, 4, idx)
plt.loglog(x, y, 'bo', label='Errore misurato')
if y_fit is not None:
plt.loglog(x, y_fit, 'r-', label=label_fit)
else:
plt.text(0.5, 0.5, 'Fit fallito', ha='center', va='center', transform=plt.gca().transAxes)
plt.title(f'{name}')
plt.xlabel('Numero primi nel primorial')
plt.ylabel('Errore assoluto')
plt.legend()
plt.grid(True, which="both", ls="--")
plt.tight_layout()
plt.show()
if __name__ == "__main__":
main()
from math import gcd
def is_proper_fraction(a, b):
return a < b
def check_fraction_reducibility(n):
u_n = n % 10 # unità
d_n = n - u_n # decine
num = u_n
den = d_n
print(f"Numero inserito: {n}")
print(f"Unità (u_n): {u_n}, Decine (n - u_n): {d_n}")
print(f"Frazione iniziale: {num}/{den}")
while is_proper_fraction(num, den):
div = gcd(num, den)
if div > 1:
reduced_num = num // div
reduced_den = den // div
print(f"Frazione ridotta trovata: {num}/{den} = {reduced_num}/{reduced_den}")
print(f"Il numero {n} NON è primo.")
print(f"Divisore comune trovato: {div}")
print(f"La somma numeratore + denominatore della frazione ridotta: {reduced_num + reduced_den}")
print(f"Quindi i due fattori primi di {n} possono essere: {div} e {n // div}")
return False
num += 1
den -= 1
print(f"Tentativo: {num}/{den}")
print(f"Nessuna riduzione trovata. Il numero {n} è PRIMO.")
return True
# Esegui qui
try:
n = int(input("Inserisci un numero intero n ≥ 10: "))
if n < 10:
print("Per favore, inserisci un numero maggiore o uguale a 10.")
else:
check_fraction_reducibility(n)
except ValueError:
print("Input non valido. Inserisci un numero intero.")
import sympy
import matplotlib.pyplot as plt
# Calcola il primorial p_i#
def primorial(i):
return sympy.prod(sympy.primerange(1, sympy.prime(i) + 1))
# Verifica se esiste un p' > p_i# tale che |p' - p_i#| è primo
def primorial_prime_distance(i, search_limit=100000):
p_hash = primorial(i)
small_primes = set(sympy.primerange(2, search_limit))
# Cerca primi p' dopo p_i#
for p_prime in sympy.primerange(p_hash + 1, p_hash + search_limit):
delta = p_prime - p_hash
if delta in small_primes:
return delta
return None
# Esegui l'esperimento fino a i_max
i_max = 100
results = []
for i in range(2, i_max + 1):
try:
d = primorial_prime_distance(i)
results.append((i, d))
if d:
print(f"[i={i}] ✓ diff = {d}")
else:
print(f"[i={i}] ✗ Nessuna differenza trovata entro il limite")
except:
results.append((i, None))
print(f"[i={i}] ⚠️ Errore durante il calcolo")
# Grafico delle differenze
valid_results = [(i, d) for i, d in results if d is not None]
x_vals = [i for i, d in valid_results]
y_vals = [d for i, d in valid_results]
plt.figure(figsize=(10, 5))
plt.plot(x_vals, y_vals, 'o-', color='green')
plt.xlabel("Indice i")
plt.ylabel("Differenza primo |p_i# - p'|")
plt.title("Distanza tra p_i# e p' tale che la differenza sia un primo")
plt.grid(True)
plt.show()
import matplotlib.pyplot as plt
import pandas as pd
import sympy as sp
import numpy as np
# Titolo e descrizione in output
titolo = "Analisi dei Primi Validati per i Primoriali"
descrizione = """
Descrizione:
Questo programma esegue un'analisi sui numeri primoriali, studiando i numeri primi che si trovano nell'intervallo definito da un primoriale e il successivo quadrato di un numero primo.
Vengono analizzati i primi che, sommati o sottratti a un primoriale, risultano essere primi. Inoltre, viene calcolata la densità di tali primi validi e un limite asintotico (CPP_bound) basato sul logaritmo naturale del primoriale.
Alla fine dell'analisi, vengono generati grafici per visualizzare i risultati: uno mostra il numero di primi validi e l'altro la densità di validi per ciascun primoriale.
"""
# Stampa titolo e descrizione
print(f"{titolo}\n")
print(f"{descrizione}\n")
def analisi_completa_cpp(inizio=6, fine=25):
risultati_completi = []
primi = list(sp.primerange(2, 1000)) # Copre almeno 25 primoriali
for i in range(inizio, fine + 1):
p_i = primi[i - 1]
p_i1 = primi[i]
p_i_primorial = sp.prod(primi[:i])
start = p_i1
end = p_i1 ** 2 + 1
intervallo = list(sp.primerange(start, end))
count_valide = 0
doppie_valide = 0
for p_ in intervallo:
plus = p_i_primorial + p_
minus = p_i_primorial - p_
plus_prime = sp.isprime(plus)
minus_prime = sp.isprime(minus)
if plus_prime or minus_prime:
count_valide += 1
if plus_prime and minus_prime:
doppie_valide += 1
# Logaritmo naturale del primoriale
log_pi_sharp = np.log(float(p_i_primorial))
cpp_bound = (log_pi_sharp ** 2) / 2 # Nuova forma asintotica
risultati_completi.append({
"i": i,
"p_i#": int(p_i_primorial),
"num_primi_nell_intervallo": len(intervallo),
"validi_±": count_valide,
"doppie": doppie_valide,
"densità_valide": count_valide / len(intervallo) if intervallo else 0,
"CPP_bound": cpp_bound
})
return pd.DataFrame(risultati_completi)
# Esegui l'analisi
df_cpp = analisi_completa_cpp(6, 25)
# --- GRAFICO 1: Numero di primi validi (±) e doppie ---
plt.figure(figsize=(12, 6))
plt.plot(df_cpp["i"], df_cpp["validi_±"], marker='o', label="Primi validi (±)")
plt.plot(df_cpp["i"], df_cpp["doppie"], marker='x', linestyle='--', label="Entrambi ± primi")
plt.title("Numero di primi $p'$ validi per ciascun primoriale $p_i\\#$")
plt.xlabel("Indice i")
plt.ylabel("Conteggio")
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
# --- GRAFICO 2: Densità dei primi validi ---
plt.figure(figsize=(12, 6))
plt.plot(df_cpp["i"], df_cpp["densità_valide"], marker='s', color='green', label="Densità di primi validi")
plt.title("Densità di $p'$ tali che $p_i\\# ± p'$ è primo su $[p_{i+1}, p_{i+1}^2]$")
plt.xlabel("Indice i")
plt.ylabel("Densità")
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
# (Facoltativo) Salvataggio CSV
# df_cpp.to_csv("risultati_cpp.csv", index=False)
# Mostra le prime righe
df_cpp.head(10)
import math
def prodotto_dispari_fino_mod(a, n):
"""Calcola il prodotto modulo n dei numeri dispari fino a 'a'."""
prodotto_mod = 1
for i in range(1, a + 1, 2):
prodotto_mod = (prodotto_mod * i) % n
if prodotto_mod == 0:
break # Il prodotto è divisibile per n, possiamo fermarci
return prodotto_mod
def stampa_descrizione():
descrizione = (
"Verifica se un numero dispari n ≥ 9 è primo o composto.\n"
"Calcola il prodotto modulo n dei numeri dispari fino a un limite calcolato in base a n.\n"
"Limite = 2 * ⌈1.5 * √n⌉ - 1 per 9 ≤ n ≤ 81;\n"
"Limite = 2 * ⌈n / 6⌉ - 1 per n > 81.\n"
"Se il prodotto modulo n è zero, n è composto; altrimenti è primo."
)
print("\n--- Descrizione sintetica ---")
for riga in descrizione.split('\n'):
print(" " + riga) # indentazione per migliore leggibilità
print()
def check_composizione(n):
if n < 9:
print("Il numero inserito è inferiore a 9. Il programma funziona solo per n ≥ 9.")
return
if n % 2 == 0:
print("Il numero inserito non è dispari.")
return
print(f"Analisi del numero dispari n = {n}:")
if 9 <= n <= 81:
limite = 2 * math.ceil(1.5 * math.sqrt(n)) - 1
print(f"Limite calcolato: 2 * ⌈1.5 * √{n}⌉ - 1 = {limite}")
else:
limite = 2 * math.ceil(n / 6) - 1
print(f"Limite calcolato: 2 * ⌈{n} / 6⌉ - 1 = {limite}")
prodotto_mod = prodotto_dispari_fino_mod(limite, n)
print(f"Prodotto modulo {n} dei numeri dispari fino a {limite}: {prodotto_mod}")
if prodotto_mod == 0:
print(f"{n} è un numero composto.\n")
else:
print(f"{n} è un numero primo.\n")
# Stampa la descrizione prima dell'input
stampa_descrizione()
# Input dell'utente (un numero dispari)
n = int(input("Inserisci un numero dispari n: "))
check_composizione(n)
import sympy
import matplotlib.pyplot as plt
def trova_coppie_primi(limite):
coppie = []
primi = list(sympy.primerange(2, limite + 1))
for p in primi:
numeratore = 3 * p**2 + 1
if numeratore % 4 == 0:
p_prime = numeratore // 4
if sympy.isprime(p_prime) and p_prime > p:
coppie.append((p, p_prime))
return coppie
def successione_continua(p_start, max_iter=20):
successione = [p_start]
p = p_start
for _ in range(max_iter):
numeratore = 3 * p**2 + 1
if numeratore % 4 != 0:
break
p_prime = numeratore // 4
if sympy.isprime(p_prime) and p_prime > p:
successione.append(p_prime)
p = p_prime
else:
break
return successione
def main():
limite = int(input("Inserisci il limite massimo per p (numero primo): "))
coppie = trova_coppie_primi(limite)
if not coppie:
print("Nessuna coppia trovata con il limite indicato.")
return
print("\nCoppie di primi (p, p') che soddisfano la formula:")
for p, p_prime in coppie:
print(f"p = {p}, p' = {p_prime}")
successioni = []
print("\nVerifica continuità della successione per ogni p:")
for p, _ in coppie:
seq = successione_continua(p, max_iter=15)
successioni.append((p, len(seq)))
print(f"Successione da p = {p}: {seq}")
# Dati per il grafico scatter p vs p'
p_vals = [p for p, p_prime in coppie]
p_prime_vals = [p_prime for p, p_prime in coppie]
# Dati per il grafico lunghezza successione
length_succ = [length for p, length in successioni]
indici = range(1, len(length_succ) + 1) # 1, 2, 3, ...
plt.figure(figsize=(12, 5))
# Grafico scatter p vs p'
plt.subplot(1, 2, 1)
plt.scatter(p_vals, p_prime_vals, color='blue')
plt.title("Distribuzione di p' rispetto a p")
plt.xlabel("p (primo)")
plt.ylabel("p' = (3p² + 1)/4 (primo)")
plt.grid(True)
# Grafico lunghezza successione senza etichette testuali sull'asse x
plt.subplot(1, 2, 2)
plt.bar(indici, length_succ, color='green')
plt.title("Lunghezza della successione per ogni p (in ordine)")
plt.xlabel("Indice successione")
plt.ylabel("Lunghezza successione")
plt.grid(axis='y')
# Rimuove le etichette testuali ma mantiene i tick numerici
plt.gca().set_xticklabels([])
plt.xticks(indici)
plt.tight_layout()
plt.show()
if __name__ == "__main__":
main()
Il programma implementa un test di primalità e genera sequenze di numeri primi basandosi su due metodi principali:
1. Test di Miller-Rabin (funzioni `miller_rabin_test` e `is_prime_miller_rabin`)
È un algoritmo probabilistico usato per verificare se un numero è probabilmente primo.
- La funzione `miller_rabin_test` esegue il test vero e proprio, basandosi su potenze modulari e condizioni che identificano se un numero è composto o primo.
- La funzione `is_prime_miller_rabin` usa più iterazioni del test con basi casuali per aumentare l’affidabilità del risultato.
2. Generazione di tutti i numeri primi fino a un certo limite (`genera_primi_fino_a`)
Usa un semplice crivello di Eratostene per generare tutti i numeri primi fino a una certa soglia.
3. Generazione di una particolare sequenza di numeri primi (`sequenza_primi_fino_non_primo`)
Partendo da un numero primo `p` generato prima, crea una sequenza definita da a_n = p + n(n + 1), incrementando `n` finché il termine calcolato è primo secondo il test di Miller-Rabin.
4. Funzione principale `main`
- Chiede all’utente di inserire un limite superiore.
- Genera tutti i numeri primi fino a quel limite con il crivello.
- Per ogni primo trovato genera e stampa la sequenza a_n definita finché i termini sono primi.
- Stampa i risultati delle sequenze generate, indicando quanti termini in sequenza rimangono primi.
In sintesi, il programma combina un test di primalità probabilistico efficace (Miller-Rabin) con un metodo classico (crivello) e analizza sequenze numeriche basate sui primi generati, individuando proprietà particolari dei primi in tali sequenze.
import random
def miller_rabin_test(d, n, a):
x = pow(a, d, n)
if x == 1 or x == n - 1:
return True
while d != n - 1:
x = (x * x) % n
d *= 2
if x == 1:
return False
if x == n - 1:
return True
return False
def is_prime_miller_rabin(n, k=5):
if n <= 1:
return False
if n <= 3:
return True
if n % 2 == 0:
return False
d = n - 1
while d % 2 == 0:
d //= 2
for _ in range(k):
a = random.randrange(2, n - 1)
if not miller_rabin_test(d, n, a):
return False
return True
def genera_primi_fino_a(limite):
"""Genera tutti i numeri primi <= limite con un semplice crivello."""
if limite < 2:
return []
sieve = [True] * (limite + 1)
sieve[0], sieve[1] = False, False
for i in range(2, int(limite**0.5) + 1):
if sieve[i]:
for j in range(i*i, limite + 1, i):
sieve[j] = False
return [i for i, primo in enumerate(sieve) if primo]
def sequenza_primi_fino_non_primo(p):
"""Genera la sequenza a_n = p + n(n+1) finché i termini sono primi."""
risultati = []
n = 0
while True:
val = p + n*(n+1)
if is_prime_miller_rabin(val):
risultati.append(val)
n += 1
else:
break
return risultati
def main():
try:
limite = int(input("Inserisci un limite superiore (intero, non necessariamente primo): "))
except ValueError:
print("Errore: inserisci un numero intero valido.")
return
primi = genera_primi_fino_a(limite)
if not primi:
print("Nessun numero primo trovato nel range specificato.")
return
print(f"Numeri primi trovati fino a {limite}: {primi}\n")
for p in primi:
seq = sequenza_primi_fino_non_primo(p)
print(f"Primo iniziale p = {p}: sequenza di {len(seq)} termini")
print(seq)
print("-" * 40)
if __name__ == "__main__":
main()
import math
def get_period_length(n):
"""
Programma che prende un limite come input e verifica, per ogni n fino a quel
limite, se la lunghezza del periodo decimale di 1/n è uguale a n−1. Se lo è,
il numero n è primo. Questo approccio si basa sulla proprietà che se il periodo
di 1/n è n−1, allora n è un numero primo a periodo pieno.
Calcola la lunghezza del periodo dell'espansione decimale di 1/n.
Questa funzione calcola l'ordine moltiplicativo di 10 modulo n,
gestendo i fattori 2 e 5.
"""
if n <= 1:
return 0
# Rimuoviamo i fattori 2 e 5 dal denominatore, poiché non influenzano il periodo.
# Questo è necessario per garantire che n sia coprimo con 10.
while n % 2 == 0:
n //= 2
while n % 5 == 0:
n //= 5
# Se n è 1 dopo aver rimosso i fattori 2 e 5, il decimale termina.
if n == 1:
return 0
# Calcoliamo la lunghezza del periodo (ordine moltiplicativo di 10 mod n)
remainder = 1
period_length = 0
# Utilizziamo un dizionario per tracciare i resti già visti e rilevare il ciclo.
remainders_seen = {}
while remainder not in remainders_seen:
remainders_seen[remainder] = period_length
remainder = (remainder * 10) % n
period_length += 1
# La lunghezza del periodo è la differenza tra la posizione corrente e
# la posizione del resto ripetuto.
return period_length - remainders_seen[remainder]
def find_full_reptend_primes(limit):
"""
Trova e stampa i numeri primi a periodo pieno (full reptend primes)
fino al limite dato dall'utente.
"""
print(f"--- Numeri Primi a Periodo Pieno fino a {limit} ---")
# Iteriamo da 2 fino al limite specificato.
for n in range(2, limit + 1):
period_length = get_period_length(n)
# Se la lunghezza del periodo è esattamente n-1, allora n è un
# numero primo a periodo pieno.
if period_length == n - 1:
print(f"{n} (Periodo: {period_length})")
# Richiediamo all'utente di inserire il limite
try:
user_input = input("Inserisci un limite massimo per la ricerca dei numeri primi a periodo pieno: ")
limit = int(user_input)
if limit < 2:
print("Il limite deve essere un numero maggiore o uguale a 2.")
else:
find_full_reptend_primes(limit)
except ValueError:
print("Input non valido. Inserisci un numero intero.")