pyopu 0.1.0__tar.gz

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.
pyopu-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyopu
3
+ Version: 0.1.0
4
+ Summary: Organoid Processing Unit (OPU) framework for Organoid Computing
5
+ Author: Neuroqudit Research & Development
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
10
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: numpy>=1.20.0
14
+ Requires-Dist: brian2>=2.5.0
15
+ Requires-Dist: matplotlib>=3.3.0
16
+ Requires-Dist: networkx>=2.5
17
+ Dynamic: author
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: requires-dist
22
+ Dynamic: requires-python
23
+ Dynamic: summary
24
+
25
+ # pyopu: Organoid Processing Unit Framework
26
+
27
+ `pyopu` is a Python framework for simulating and executing combinatorial optimization tasks on biological wetware (Brain Organoids) using the **Holographic Tensorial Paradigm**.
28
+
29
+ ## Features
30
+ - **Agnostic Math Engine:** Compile NP-Hard problems into Rank-2 and Rank-3 Tensors.
31
+ - **Interchangeable Backends:** Run problems on virtual MEAs (Microelectrode Arrays) or advanced Dual-Opsin Optogenetic interfaces.
32
+ - **Observer Telemetry:** Extract spikes, opsin kinetics, and energy dynamics cleanly without cluttering the engine.
33
+
34
+ ## Quickstart
35
+ Check the `examples/` directory for full implementations of the Max-Cut and 3-SAT problems.
pyopu-0.1.0/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # pyopu: Organoid Processing Unit Framework
2
+
3
+ `pyopu` is a Python framework for simulating and executing combinatorial optimization tasks on biological wetware (Brain Organoids) using the **Holographic Tensorial Paradigm**.
4
+
5
+ ## Features
6
+ - **Agnostic Math Engine:** Compile NP-Hard problems into Rank-2 and Rank-3 Tensors.
7
+ - **Interchangeable Backends:** Run problems on virtual MEAs (Microelectrode Arrays) or advanced Dual-Opsin Optogenetic interfaces.
8
+ - **Observer Telemetry:** Extract spikes, opsin kinetics, and energy dynamics cleanly without cluttering the engine.
9
+
10
+ ## Quickstart
11
+ Check the `examples/` directory for full implementations of the Max-Cut and 3-SAT problems.
@@ -0,0 +1 @@
1
+ from .engine import TensorEngine, OPUResult
@@ -0,0 +1,55 @@
1
+ import numpy as np
2
+ from pyopu.tensors.base_tensor import Rank1Tensor, Rank2Tensor, Rank3Tensor
3
+ from pyopu.telemetry.base_telemetry import Observable
4
+
5
+ class OPUResult:
6
+ def __init__(self, final_probabilities):
7
+ self.final_probabilities = final_probabilities
8
+
9
+ def get_binary_state(self, threshold=0.5):
10
+ return [1 if p > threshold else 0 for p in self.final_probabilities]
11
+
12
+ class TensorEngine(Observable):
13
+ def __init__(self, organoid, interface):
14
+ super().__init__()
15
+ self.organoid = organoid
16
+ self.interface = interface
17
+ self.T1 = None; self.T2 = None; self.T3 = None
18
+
19
+ def set_tensors(self, t1: Rank1Tensor, t2: Rank2Tensor = None, t3: Rank3Tensor = None):
20
+ nv = t1.num_vars
21
+ if t2 is not None and t2.num_vars != nv: raise ValueError("Rank-2 var mismatch.")
22
+ if t3 is not None and t3.num_vars != nv: raise ValueError("Rank-3 var mismatch.")
23
+ self.T1 = t1.data
24
+ self.T2 = t2.data if t2 is not None else None
25
+ self.T3 = t3.data if t3 is not None else None
26
+
27
+ def _calculate_force(self):
28
+ Force = np.copy(self.T1)
29
+ if self.T2 is not None:
30
+ Force += np.dot(self.T2, self.organoid.x_state) + np.dot(self.T2.T, self.organoid.x_state)
31
+ if self.T3 is not None:
32
+ F3 = np.einsum('ijk,j,k->i', self.T3, self.organoid.x_state, self.organoid.x_state)
33
+ F3 += np.einsum('jik,j,k->i', self.T3, self.organoid.x_state, self.organoid.x_state)
34
+ F3 += np.einsum('jki,j,k->i', self.T3, self.organoid.x_state, self.organoid.x_state)
35
+ Force += F3
36
+ return Force
37
+
38
+ def run(self, duration_ms=1000.0, dt_kernel_ms=10.0):
39
+ if self.T1 is None: raise ValueError("Tensors not set.")
40
+ steps = int(duration_ms / dt_kernel_ms)
41
+ print(f"Starting OPU Simulation ({self.organoid.num_neobits} Neobits)...")
42
+
43
+ for step in range(steps):
44
+ x_inst = self.interface.read_from_organoid(self.organoid._control_spike_mon, self.organoid.neurons_per_neobit, self.organoid.num_neobits)
45
+ self.organoid.x_state = 0.8 * self.organoid.x_state + 0.2 * x_inst
46
+ force = self._calculate_force()
47
+ self.interface.write_to_organoid(self.organoid.neuron_group, force, self.organoid.neurons_per_neobit)
48
+
49
+ # Notify observers (like EnergyTelemetry)
50
+ self.notify_telemetries(x_state=self.organoid.x_state, force=force)
51
+
52
+ self.organoid.run_step(dt_kernel_ms)
53
+
54
+ print("Execution Complete.")
55
+ return OPUResult(self.organoid.x_state)
File without changes
@@ -0,0 +1,9 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ class BaseInterface(ABC):
4
+ @abstractmethod
5
+ def get_biological_equations(self): pass
6
+ @abstractmethod
7
+ def read_from_organoid(self, spike_monitor, neurons_per_neobit, num_neobits): pass
8
+ @abstractmethod
9
+ def write_to_organoid(self, neuron_group, force_array, neurons_per_neobit): pass
@@ -0,0 +1,30 @@
1
+ import numpy as np
2
+ import brian2 as b2
3
+ from .base_interface import BaseInterface
4
+
5
+ class MEAInterface(BaseInterface):
6
+ def __init__(self, temperature=1.0, max_current_na=2.0):
7
+ self.T_sys = temperature
8
+ self.max_current = max_current_na * b2.nA
9
+ self.last_spike_count = None
10
+
11
+ def get_biological_equations(self):
12
+ return '''
13
+ I_stim = I_op : amp
14
+ I_op : amp
15
+ '''
16
+
17
+ def read_from_organoid(self, spike_monitor, neurons_per_neobit, num_neobits):
18
+ if self.last_spike_count is None: self.last_spike_count = np.zeros(num_neobits * neurons_per_neobit)
19
+ curr = np.array(spike_monitor.count)
20
+ delta = curr - self.last_spike_count
21
+ self.last_spike_count = curr
22
+ x_inst = np.zeros(num_neobits)
23
+ for i in range(num_neobits):
24
+ if np.sum(delta[i*neurons_per_neobit : (i+1)*neurons_per_neobit]) > 1: x_inst[i] = 1.0
25
+ return x_inst
26
+
27
+ def write_to_organoid(self, neuron_group, force_array, neurons_per_neobit):
28
+ prob = 1.0 / (1.0 + np.exp(force_array / self.T_sys))
29
+ currents = (prob - 0.5) * 2.0 * self.max_current
30
+ neuron_group.I_op = np.repeat(currents, neurons_per_neobit)
@@ -0,0 +1,40 @@
1
+ import numpy as np
2
+ import brian2 as b2
3
+ from .base_interface import BaseInterface
4
+
5
+ class OptoInterface(BaseInterface):
6
+ def __init__(self, temperature=1.0, max_rate_hz=200.0):
7
+ self.T_sys = temperature
8
+ self.max_rate_hz = max_rate_hz
9
+ self.last_spike_count = None
10
+
11
+ def get_biological_equations(self):
12
+ return '''
13
+ I_stim = I_blue + I_yellow : amp
14
+ I_blue = 2.0 * nA * O_blue * (0*mV - v) / (60*mV) : amp
15
+ I_yellow = 2.0 * nA * O_yellow * (-90*mV - v) / (60*mV) : amp
16
+ dO_blue/dt = (phi_blue * (1 - O_blue) - 0.1/ms * O_blue) : 1
17
+ dO_yellow/dt = (phi_yellow * (1 - O_yellow) - 0.05/ms * O_yellow) : 1
18
+ phi_blue : Hz
19
+ phi_yellow : Hz
20
+ '''
21
+
22
+ def read_from_organoid(self, spike_monitor, neurons_per_neobit, num_neobits):
23
+ if self.last_spike_count is None: self.last_spike_count = np.zeros(num_neobits * neurons_per_neobit)
24
+ curr = np.array(spike_monitor.count)
25
+ delta = curr - self.last_spike_count
26
+ self.last_spike_count = curr
27
+ x_inst = np.zeros(num_neobits)
28
+ for i in range(num_neobits):
29
+ if np.sum(delta[i*neurons_per_neobit : (i+1)*neurons_per_neobit]) > 1: x_inst[i] = 1.0
30
+ return x_inst
31
+
32
+ def write_to_organoid(self, neuron_group, force_array, neurons_per_neobit):
33
+ num_neobits = len(force_array)
34
+ prob = 1.0 / (1.0 + np.exp(force_array / self.T_sys))
35
+ blue = np.zeros(num_neobits); yellow = np.zeros(num_neobits)
36
+ for i in range(num_neobits):
37
+ if prob[i] > 0.5: blue[i] = (prob[i] - 0.5) * 2.0
38
+ else: yellow[i] = (0.5 - prob[i]) * 2.0
39
+ neuron_group.phi_blue = np.repeat(blue, neurons_per_neobit) * self.max_rate_hz * b2.Hz
40
+ neuron_group.phi_yellow = np.repeat(yellow, neurons_per_neobit) * self.max_rate_hz * b2.Hz
File without changes
@@ -0,0 +1,5 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ class BaseOrganoid(ABC):
4
+ @abstractmethod
5
+ def run_step(self, dt_ms): pass
@@ -0,0 +1,34 @@
1
+ import numpy as np
2
+ import brian2 as b2
3
+ from .base_organoid import BaseOrganoid
4
+ from pyopu.telemetry.base_telemetry import Observable
5
+
6
+ class LIFOrganoid(BaseOrganoid, Observable):
7
+ def __init__(self, num_neobits, neurons_per_neobit, interface, noise_sigma=5.0):
8
+ Observable.__init__(self)
9
+ self.num_neobits = num_neobits
10
+ self.neurons_per_neobit = neurons_per_neobit
11
+ self.x_state = np.zeros(num_neobits)
12
+
13
+ b2.start_scope()
14
+ b2.defaultclock.dt = 0.1 * b2.ms
15
+
16
+ base_eqs = '''
17
+ dv/dt = (-70*mV - v + 100*Mohm * I_stim + noise_sigma * (20*ms)**-0.5 * xi) / (20*ms) : volt (unless refractory)
18
+ '''
19
+ full_eqs = base_eqs + interface.get_biological_equations()
20
+
21
+ self.neuron_group = b2.NeuronGroup(
22
+ num_neobits * neurons_per_neobit, full_eqs,
23
+ threshold='v > -50*mV', reset='v = -75*mV', refractory=2*b2.ms, method='heun',
24
+ namespace={'noise_sigma': noise_sigma * b2.mV}
25
+ )
26
+ self.neuron_group.v = '-70*mV + rand() * 20*mV'
27
+ self._control_spike_mon = b2.SpikeMonitor(self.neuron_group)
28
+ self.network = b2.Network(self.neuron_group, self._control_spike_mon)
29
+
30
+ def register_hardware(self, brian_object):
31
+ self.network.add(brian_object)
32
+
33
+ def run_step(self, dt_ms):
34
+ self.network.run(dt_ms * b2.ms)
File without changes
@@ -0,0 +1,25 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ class BaseTelemetry(ABC):
4
+ @abstractmethod
5
+ def attach(self, subject):
6
+ pass
7
+
8
+ def update(self, *args, **kwargs):
9
+ pass
10
+
11
+ @abstractmethod
12
+ def get_data(self):
13
+ pass
14
+
15
+ class Observable:
16
+ def __init__(self):
17
+ self._telemetries = []
18
+
19
+ def attach_telemetry(self, telemetry: BaseTelemetry):
20
+ self._telemetries.append(telemetry)
21
+ telemetry.attach(self)
22
+
23
+ def notify_telemetries(self, *args, **kwargs):
24
+ for telemetry in self._telemetries:
25
+ telemetry.update(*args, **kwargs)
@@ -0,0 +1,37 @@
1
+ import brian2 as b2
2
+ from .base_telemetry import BaseTelemetry
3
+
4
+ class SpikeTelemetry(BaseTelemetry):
5
+ def __init__(self):
6
+ self.spike_monitor = None
7
+
8
+ def attach(self, organoid):
9
+ self.spike_monitor = b2.SpikeMonitor(organoid.neuron_group)
10
+ organoid.register_hardware(self.spike_monitor)
11
+
12
+ def get_data(self):
13
+ return {
14
+ "times_ms": self.spike_monitor.t / b2.ms,
15
+ "neuron_ids": self.spike_monitor.i
16
+ }
17
+
18
+ class OpsinTelemetry(BaseTelemetry):
19
+ def __init__(self, record_indices):
20
+ self.record_indices = record_indices
21
+ self.state_monitor = None
22
+
23
+ def attach(self, organoid):
24
+ if 'O_blue' in organoid.neuron_group.equations.names:
25
+ self.state_monitor = b2.StateMonitor(
26
+ organoid.neuron_group, ['O_blue', 'O_yellow'], record=self.record_indices
27
+ )
28
+ organoid.register_hardware(self.state_monitor)
29
+ else:
30
+ raise ValueError("OpsinTelemetry attached to an organoid without Opsins.")
31
+
32
+ def get_data(self):
33
+ return {
34
+ "times_ms": self.state_monitor.t / b2.ms,
35
+ "O_blue": self.state_monitor.O_blue,
36
+ "O_yellow": self.state_monitor.O_yellow
37
+ }
@@ -0,0 +1,16 @@
1
+ import numpy as np
2
+ from .base_telemetry import BaseTelemetry
3
+
4
+ class EnergyTelemetry(BaseTelemetry):
5
+ def __init__(self):
6
+ self.history_energy = []
7
+
8
+ def attach(self, engine):
9
+ pass
10
+
11
+ def update(self, x_state, force):
12
+ energy = -np.sum(x_state * force)
13
+ self.history_energy.append(energy)
14
+
15
+ def get_data(self):
16
+ return {"energy_over_time": self.history_energy}
@@ -0,0 +1,37 @@
1
+ import brian2 as b2
2
+ from .base_telemetry import BaseTelemetry
3
+
4
+ class MeaTelemetry(BaseTelemetry):
5
+ def __init__(self, record_indices):
6
+ self.record_indices = record_indices
7
+ self.state_monitor = None
8
+
9
+ def attach(self, organoid):
10
+ if 'I_op' not in organoid.neuron_group.equations.names:
11
+ raise ValueError("MeaTelemetry attached to non-MEA organoid.")
12
+ self.state_monitor = b2.StateMonitor(organoid.neuron_group, ['I_op'], record=self.record_indices)
13
+ organoid.register_hardware(self.state_monitor)
14
+
15
+ def get_data(self):
16
+ return {
17
+ "times_ms": self.state_monitor.t / b2.ms,
18
+ "injected_current_nA": self.state_monitor.I_op / b2.nA
19
+ }
20
+
21
+ class OptoTelemetry(BaseTelemetry):
22
+ def __init__(self, record_indices):
23
+ self.record_indices = record_indices
24
+ self.state_monitor = None
25
+
26
+ def attach(self, organoid):
27
+ if 'phi_blue' not in organoid.neuron_group.equations.names:
28
+ raise ValueError("OptoTelemetry attached to non-Opto organoid.")
29
+ self.state_monitor = b2.StateMonitor(organoid.neuron_group, ['phi_blue', 'phi_yellow'], record=self.record_indices)
30
+ organoid.register_hardware(self.state_monitor)
31
+
32
+ def get_data(self):
33
+ return {
34
+ "times_ms": self.state_monitor.t / b2.ms,
35
+ "light_blue_hz": self.state_monitor.phi_blue / b2.Hz,
36
+ "light_yellow_hz": self.state_monitor.phi_yellow / b2.Hz
37
+ }
File without changes
@@ -0,0 +1,28 @@
1
+ import numpy as np
2
+
3
+ class Tensor:
4
+ def __init__(self, data, expected_rank):
5
+ self._data = np.array(data, dtype=np.float64)
6
+ self.rank = expected_rank
7
+ if self._data.ndim != self.rank:
8
+ raise ValueError(f"Expected Rank-{self.rank}, got dim={self._data.ndim}.")
9
+ self.shape = self._data.shape
10
+ if self.rank > 1 and not all(dim == self.shape[0] for dim in self.shape):
11
+ raise ValueError(f"Rank-{self.rank} Tensor must be hypercubic. Shape: {self.shape}.")
12
+ self.num_vars = self.shape[0] if self.shape else 0
13
+
14
+ @property
15
+ def data(self):
16
+ return self._data
17
+
18
+ class Rank1Tensor(Tensor):
19
+ def __init__(self, data):
20
+ super().__init__(data, expected_rank=1)
21
+
22
+ class Rank2Tensor(Tensor):
23
+ def __init__(self, data):
24
+ super().__init__(data, expected_rank=2)
25
+
26
+ class Rank3Tensor(Tensor):
27
+ def __init__(self, data):
28
+ super().__init__(data, expected_rank=3)
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyopu
3
+ Version: 0.1.0
4
+ Summary: Organoid Processing Unit (OPU) framework for Organoid Computing
5
+ Author: Neuroqudit Research & Development
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
10
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: numpy>=1.20.0
14
+ Requires-Dist: brian2>=2.5.0
15
+ Requires-Dist: matplotlib>=3.3.0
16
+ Requires-Dist: networkx>=2.5
17
+ Dynamic: author
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: requires-dist
22
+ Dynamic: requires-python
23
+ Dynamic: summary
24
+
25
+ # pyopu: Organoid Processing Unit Framework
26
+
27
+ `pyopu` is a Python framework for simulating and executing combinatorial optimization tasks on biological wetware (Brain Organoids) using the **Holographic Tensorial Paradigm**.
28
+
29
+ ## Features
30
+ - **Agnostic Math Engine:** Compile NP-Hard problems into Rank-2 and Rank-3 Tensors.
31
+ - **Interchangeable Backends:** Run problems on virtual MEAs (Microelectrode Arrays) or advanced Dual-Opsin Optogenetic interfaces.
32
+ - **Observer Telemetry:** Extract spikes, opsin kinetics, and energy dynamics cleanly without cluttering the engine.
33
+
34
+ ## Quickstart
35
+ Check the `examples/` directory for full implementations of the Max-Cut and 3-SAT problems.
@@ -0,0 +1,23 @@
1
+ README.md
2
+ setup.py
3
+ pyopu/__init__.py
4
+ pyopu/engine.py
5
+ pyopu.egg-info/PKG-INFO
6
+ pyopu.egg-info/SOURCES.txt
7
+ pyopu.egg-info/dependency_links.txt
8
+ pyopu.egg-info/requires.txt
9
+ pyopu.egg-info/top_level.txt
10
+ pyopu/interfaces/__init__.py
11
+ pyopu/interfaces/base_interface.py
12
+ pyopu/interfaces/mea_interface.py
13
+ pyopu/interfaces/opto_interface.py
14
+ pyopu/organoids/__init__.py
15
+ pyopu/organoids/base_organoid.py
16
+ pyopu/organoids/lif_organoid.py
17
+ pyopu/telemetry/__init__.py
18
+ pyopu/telemetry/base_telemetry.py
19
+ pyopu/telemetry/bio_telemetry.py
20
+ pyopu/telemetry/engine_telemetry.py
21
+ pyopu/telemetry/interface_telemetry.py
22
+ pyopu/tensors/__init__.py
23
+ pyopu/tensors/base_tensor.py
@@ -0,0 +1,4 @@
1
+ numpy>=1.20.0
2
+ brian2>=2.5.0
3
+ matplotlib>=3.3.0
4
+ networkx>=2.5
@@ -0,0 +1 @@
1
+ pyopu
pyopu-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
pyopu-0.1.0/setup.py ADDED
@@ -0,0 +1,28 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name="pyopu",
8
+ version="0.1.0",
9
+ author="Neuroqudit Research & Development",
10
+ description="Organoid Processing Unit (OPU) framework for Organoid Computing",
11
+ long_description=long_description,
12
+ long_description_content_type="text/markdown",
13
+ packages=find_packages(),
14
+ install_requires=[
15
+ "numpy>=1.20.0",
16
+ "brian2>=2.5.0",
17
+ "matplotlib>=3.3.0",
18
+ "networkx>=2.5"
19
+ ],
20
+ classifiers=[
21
+ "Programming Language :: Python :: 3",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Operating System :: OS Independent",
24
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
25
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
26
+ ],
27
+ python_requires='>=3.8',
28
+ )