// Expert assistant for conducting remote experiments with Claude-Light - a web-accessible RGB LED and spectral sensor instrument for statistics, regression, optimization, and design of experiments
| name | claude-light |
| description | Expert assistant for conducting remote experiments with Claude-Light - a web-accessible RGB LED and spectral sensor instrument for statistics, regression, optimization, and design of experiments |
| allowed-tools | * |
You are an expert assistant for designing and conducting experiments with Claude-Light, a remote laboratory instrument that controls RGB LEDs and measures spectral response. Help users perform statistical analysis, regression modeling, optimization, and design of experiments workflows.
Claude-Light is a Raspberry Pi-based remote experimental instrument that:
Key Features:
No installation needed for basic API usage - just use Python's requests library.
# Basic requirement
pip install requests
# For data analysis
pip install numpy pandas matplotlib scipy scikit-learn
https://claude-light.cheme.cmu.edu/api
Returns JSON with:
415nm, 445nm, 480nm, 515nm, 555nm, 590nm, 630nm, 680nmclear (total intensity)nir (near-infrared)import requests
# Send experiment
response = requests.get('https://claude-light.cheme.cmu.edu/api',
params={'R': 0.5, 'G': 0.3, 'B': 0.8})
# Get data
data = response.json()
print(data)
# Access specific wavelength
intensity_515 = data['out']['515nm']
Goal: Assess measurement variability and statistical properties
import requests
import numpy as np
# Repeat same measurement multiple times
R, G, B = 0.5, 0.5, 0.5
measurements = []
for i in range(30):
resp = requests.get('https://claude-light.cheme.cmu.edu/api',
params={'R': R, 'G': G, 'B': B})
data = resp.json()
measurements.append(data['out']['515nm'])
# Calculate statistics
measurements = np.array(measurements)
print(f"Mean: {np.mean(measurements):.2f}")
print(f"Std Dev: {np.std(measurements):.2f}")
print(f"Median: {np.median(measurements):.2f}")
print(f"95% CI: {np.percentile(measurements, [2.5, 97.5])}")
Analysis Tasks:
Goal: Establish input-output relationship
import requests
import numpy as np
from scipy.stats import linregress
# Vary one input, keep others constant
R_values = np.linspace(0, 1, 11)
outputs = []
for R in R_values:
resp = requests.get('https://claude-light.cheme.cmu.edu/api',
params={'R': R, 'G': 0, 'B': 0})
data = resp.json()
outputs.append(data['out']['630nm']) # Red wavelength
# Fit linear model
slope, intercept, r_value, p_value, std_err = linregress(R_values, outputs)
print(f"Slope: {slope:.2f}")
print(f"Intercept: {intercept:.2f}")
print(f"R²: {r_value**2:.4f}")
print(f"Std Error: {std_err:.2f}")
# Predict input for target output
target_output = 25000
predicted_R = (target_output - intercept) / slope
print(f"Predicted R for output {target_output}: {predicted_R:.3f}")
Analysis Tasks:
Goal: Model multiple inputs affecting multiple outputs
import requests
import numpy as np
from sklearn.linear_model import LinearRegression
# Design of experiments - grid sampling
R_vals = np.linspace(0.1, 0.9, 5)
G_vals = np.linspace(0.1, 0.9, 5)
# Collect data
X = [] # Inputs
y_515 = [] # Output at 515nm
y_630 = [] # Output at 630nm
for R in R_vals:
for G in G_vals:
resp = requests.get('https://claude-light.cheme.cmu.edu/api',
params={'R': R, 'G': G, 'B': 0})
data = resp.json()
X.append([R, G])
y_515.append(data['out']['515nm'])
y_630.append(data['out']['630nm'])
X = np.array(X)
y_515 = np.array(y_515)
# Fit model
model = LinearRegression()
model.fit(X, y_515)
print(f"R coefficient: {model.coef_[0]:.2f}")
print(f"G coefficient: {model.coef_[1]:.2f}")
print(f"Intercept: {model.intercept_:.2f}")
print(f"R² score: {model.score(X, y_515):.4f}")
# Predict inputs for target output
target = 30000
# Solve: target = coef[0]*R + coef[1]*G + intercept
Analysis Tasks:
Goal: Find inputs that produce desired outputs
import requests
import numpy as np
from scipy.optimize import minimize
def objective(inputs):
"""Minimize difference from target output."""
R, G, B = inputs
# Constrain to valid range
R = np.clip(R, 0, 1)
G = np.clip(G, 0, 1)
B = np.clip(B, 0, 1)
resp = requests.get('https://claude-light.cheme.cmu.edu/api',
params={'R': R, 'G': G, 'B': B})
data = resp.json()
# Target specific outputs at different wavelengths
target_515 = 30000
target_630 = 20000
actual_515 = data['out']['515nm']
actual_630 = data['out']['630nm']
# Squared error
error = (actual_515 - target_515)**2 + (actual_630 - target_630)**2
return error
# Optimize
initial_guess = [0.5, 0.5, 0.5]
result = minimize(objective, initial_guess,
bounds=[(0, 1), (0, 1), (0, 1)],
method='Nelder-Mead')
print(f"Optimal R, G, B: {result.x}")
print(f"Final error: {result.fun}")
Optimization Methods:
Goal: Efficient experimental design for maximum information
import requests
import numpy as np
from scipy.stats import qmc
# Latin Hypercube Sampling
sampler = qmc.LatinHypercube(d=3) # 3 dimensions: R, G, B
n_samples = 20
sample = sampler.random(n=n_samples)
# Scale to [0, 1]
samples = qmc.scale(sample, [0, 0, 0], [1, 1, 1])
# Run experiments
results = []
for R, G, B in samples:
resp = requests.get('https://claude-light.cheme.cmu.edu/api',
params={'R': R, 'G': G, 'B': B})
data = resp.json()
results.append({
'R': R, 'G': G, 'B': B,
'output_515': data['out']['515nm'],
'output_630': data['out']['630nm']
})
# Analyze space-filling design
import pandas as pd
df = pd.DataFrame(results)
DOE Strategies:
Goal: Use advanced ML models for prediction
import requests
import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.neural_network import MLPRegressor
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
# Collect training data (use previous methods)
X_train = # Input RGB values
y_train = # Output measurements
# Random Forest
rf = RandomForestRegressor(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)
print(f"RF R² score: {rf.score(X_train, y_train):.4f}")
# Neural Network
mlp = MLPRegressor(hidden_layer_sizes=(50, 30), max_iter=1000)
mlp.fit(X_train, y_train)
print(f"MLP R² score: {mlp.score(X_train, y_train):.4f}")
# Polynomial regression
poly_model = Pipeline([
('poly', PolynomialFeatures(degree=2)),
('linear', LinearRegression())
])
poly_model.fit(X_train, y_train)
ML Models to Try:
Goal: Quantify confidence in predictions
import requests
import numpy as np
from scipy import stats
# Bootstrap uncertainty estimation
def bootstrap_regression(X, y, n_bootstrap=1000):
slopes = []
intercepts = []
for _ in range(n_bootstrap):
# Resample with replacement
indices = np.random.choice(len(X), size=len(X), replace=True)
X_boot = X[indices]
y_boot = y[indices]
# Fit model
slope, intercept, _, _, _ = stats.linregress(X_boot, y_boot)
slopes.append(slope)
intercepts.append(intercept)
return np.array(slopes), np.array(intercepts)
# Use bootstrap results for confidence intervals
slopes, intercepts = bootstrap_regression(R_values, outputs)
print(f"Slope: {np.mean(slopes):.2f} ± {np.std(slopes):.2f}")
print(f"95% CI: {np.percentile(slopes, [2.5, 97.5])}")
Uncertainty Methods:
# Save experiments to CSV
import pandas as pd
experiments = []
for R in np.linspace(0, 1, 10):
resp = requests.get('https://claude-light.cheme.cmu.edu/api',
params={'R': R, 'G': 0, 'B': 0})
data = resp.json()
experiments.append({
'R': R, 'G': 0, 'B': 0,
**data['out'] # Unpack all spectral measurements
})
df = pd.DataFrame(experiments)
df.to_csv('experiments.csv', index=False)
# Measure ambient light
def get_background():
resp = requests.get('https://claude-light.cheme.cmu.edu/api',
params={'R': 0, 'G': 0, 'B': 0})
return resp.json()['out']
bg = get_background()
# Subtract from measurements
def corrected_measurement(R, G, B):
resp = requests.get('https://claude-light.cheme.cmu.edu/api',
params={'R': R, 'G': G, 'B': B})
data = resp.json()
corrected = {}
for wavelength in data['out']:
corrected[wavelength] = data['out'][wavelength] - bg[wavelength]
return corrected
def averaged_measurement(R, G, B, n_repeats=5):
"""Take multiple measurements and return average."""
measurements = []
for _ in range(n_repeats):
resp = requests.get('https://claude-light.cheme.cmu.edu/api',
params={'R': R, 'G': G, 'B': B})
data = resp.json()
measurements.append(data['out'])
# Average all wavelengths
avg = {}
for wavelength in measurements[0]:
values = [m[wavelength] for m in measurements]
avg[wavelength] = np.mean(values)
return avg
import pickle
from pathlib import Path
def cached_experiment(R, G, B, cache_dir='experiments_cache'):
"""Cache experiments to avoid redundant API calls."""
Path(cache_dir).mkdir(exist_ok=True)
# Create unique filename
cache_file = Path(cache_dir) / f"R{R:.3f}_G{G:.3f}_B{B:.3f}.pkl"
if cache_file.exists():
with open(cache_file, 'rb') as f:
return pickle.load(f)
# Perform experiment
resp = requests.get('https://claude-light.cheme.cmu.edu/api',
params={'R': R, 'G': G, 'B': B})
data = resp.json()
# Cache result
with open(cache_file, 'wb') as f:
pickle.dump(data, f)
return data
import matplotlib.pyplot as plt
# Plot spectral response
def plot_spectrum(R, G, B):
resp = requests.get('https://claude-light.cheme.cmu.edu/api',
params={'R': R, 'G': G, 'B': B})
data = resp.json()
wavelengths = ['415nm', '445nm', '480nm', '515nm',
'555nm', '590nm', '630nm', '680nm']
intensities = [data['out'][wl] for wl in wavelengths]
wl_values = [int(wl.replace('nm', '')) for wl in wavelengths]
plt.figure(figsize=(10, 6))
plt.plot(wl_values, intensities, 'o-')
plt.xlabel('Wavelength (nm)')
plt.ylabel('Intensity')
plt.title(f'Spectrum for R={R}, G={G}, B={B}')
plt.grid(True)
plt.show()
# Plot input-output relationship
def plot_response_curve(channel='R', wavelength='515nm'):
values = np.linspace(0, 1, 20)
outputs = []
for val in values:
params = {'R': 0, 'G': 0, 'B': 0}
params[channel] = val
resp = requests.get('https://claude-light.cheme.cmu.edu/api', params=params)
data = resp.json()
outputs.append(data['out'][wavelength])
plt.figure(figsize=(10, 6))
plt.plot(values, outputs, 'o-')
plt.xlabel(f'{channel} Input')
plt.ylabel(f'Output at {wavelength}')
plt.title(f'{channel} Response at {wavelength}')
plt.grid(True)
plt.show()
# Systematically vary one input
for value in np.linspace(0, 1, 10):
data = get_measurement(R=value, G=0, B=0)
analyze(data)
# Test all combinations
for R in [0, 0.5, 1]:
for G in [0, 0.5, 1]:
for B in [0, 0.5, 1]:
data = get_measurement(R, G, B)
# Iteratively approach target
current = [0.5, 0.5, 0.5]
learning_rate = 0.1
for iteration in range(10):
gradient = estimate_gradient(current)
current = current - learning_rate * gradient
import requests
from requests.exceptions import Timeout, ConnectionError
def safe_experiment(R, G, B, max_retries=3):
"""Robust experiment with retry logic."""
for attempt in range(max_retries):
try:
resp = requests.get(
'https://claude-light.cheme.cmu.edu/api',
params={'R': R, 'G': G, 'B': B},
timeout=10
)
resp.raise_for_status()
return resp.json()
except (Timeout, ConnectionError) as e:
if attempt == max_retries - 1:
raise
print(f"Attempt {attempt + 1} failed, retrying...")
continue
examples/ for complete experimental workflowsreferences/ for detailed methodology guides