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 +35 -0
- pyopu-0.1.0/README.md +11 -0
- pyopu-0.1.0/pyopu/__init__.py +1 -0
- pyopu-0.1.0/pyopu/engine.py +55 -0
- pyopu-0.1.0/pyopu/interfaces/__init__.py +0 -0
- pyopu-0.1.0/pyopu/interfaces/base_interface.py +9 -0
- pyopu-0.1.0/pyopu/interfaces/mea_interface.py +30 -0
- pyopu-0.1.0/pyopu/interfaces/opto_interface.py +40 -0
- pyopu-0.1.0/pyopu/organoids/__init__.py +0 -0
- pyopu-0.1.0/pyopu/organoids/base_organoid.py +5 -0
- pyopu-0.1.0/pyopu/organoids/lif_organoid.py +34 -0
- pyopu-0.1.0/pyopu/telemetry/__init__.py +0 -0
- pyopu-0.1.0/pyopu/telemetry/base_telemetry.py +25 -0
- pyopu-0.1.0/pyopu/telemetry/bio_telemetry.py +37 -0
- pyopu-0.1.0/pyopu/telemetry/engine_telemetry.py +16 -0
- pyopu-0.1.0/pyopu/telemetry/interface_telemetry.py +37 -0
- pyopu-0.1.0/pyopu/tensors/__init__.py +0 -0
- pyopu-0.1.0/pyopu/tensors/base_tensor.py +28 -0
- pyopu-0.1.0/pyopu.egg-info/PKG-INFO +35 -0
- pyopu-0.1.0/pyopu.egg-info/SOURCES.txt +23 -0
- pyopu-0.1.0/pyopu.egg-info/dependency_links.txt +1 -0
- pyopu-0.1.0/pyopu.egg-info/requires.txt +4 -0
- pyopu-0.1.0/pyopu.egg-info/top_level.txt +1 -0
- pyopu-0.1.0/setup.cfg +4 -0
- pyopu-0.1.0/setup.py +28 -0
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,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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyopu
|
pyopu-0.1.0/setup.cfg
ADDED
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
|
+
)
|