Appending structured data to a class attribute in Python - python

I have a couple of objects I'm using for running numerical simulations. A minimal example is shown below where there are two objects: 1) an Environment object which has two states (x and y) that it simulates stochastically through time; and 2) a Simulation object which manages the simulaton and saves the state of the Environment throughout the simulation.
Within the Simulation object, I want to save the state of the Environment both 1) through time, and 2) across multiple simulations. Through time I can use a defaultdict to save the state variables within a single simulation but across simulations it's not clear to me the best way to save the defaultdicts that have been generated. If I append to a list (without using copy) then the list returns all identical defaultdicts due to the mutability of lists. In the example below I use copy.copy, as the answer here suggests.
Are there approaches that are more "Pythonic"? Would it be better to use an immutable type to store the defaultdicts for each simulation?
import copy
from collections import defaultdict
import numpy as np, pandas as pd
from matplotlib import pyplot as plt
class Environment(object):
"""
Class representing a random walk of two variables x and y
Methods
-------
start_simulation: draw values from state variables from priors
step: add random noise to state variables
current_state: return current state of x and y in a dict
"""
def __init__(self, mu1, sigma1, mu2, sigma2):
self.mu1 = mu1
self.mu2 = mu2
self.sigma1 = sigma1
self.sigma2 = sigma2
def start_simulation(self):
self.x = self.mu1 + self.sigma1 * np.random.randn()
self.y = self.mu2 + self.sigma2 * np.random.randn()
def step(self):
self.x += self.sigma1 * np.random.randn()
self.y += self.sigma2 * np.random.randn()
def current_state(self):
return({"x": self.x, "y": self.y})
class Simulation(object):
"""
Class representing a simulation object for handling the Environment object
and storing data
Methods
-------
start_simulation: start the simulation; initialise state of the environment
simulate: generate n_simulations simulations of n_timesteps time steps each
save_state:
"""
def __init__(self, env, n_timesteps):
self.env = env
self.n_timesteps = n_timesteps
self.data_all = []
self.data_states = defaultdict(list)
def start_simulation(self):
self.timestep = 0
self.env.start_simulation()
# Append current data (if non empty)
if self.data_states:
self.data_all.append(copy.copy(self.data_states)) # <---------- this step
# without copy.copy this will return all elements of the list data_all to be the
# same default dict at the end of all simulations - lists are mutable
# Reset data_current
self.data_states = defaultdict(list)
def simulate(self, n_simulations):
"""
Run simulation for n_simulations and n_timesteps timesteps
"""
self.start_simulation()
for self.simulation in range(n_simulations):
self.timestep = 0
while(self.timestep < self.n_timesteps):
self.env.step()
self.save_state(self.env.current_state())
self.timestep += 1
self.start_simulation()
def save_state(self, state):
"""
Save results to a default dict
"""
for key, value in state.items():
self.data_states[key].append(value)
if __name__ == "__main__":
# Run 7 simulations, each for for 20 time steps
N_TIME = 20
N_SIM = 7
e = Environment(
mu1 = 1.4, sigma1 = 0.1,
mu2 = 2.6, sigma2 = 0.05)
s = Simulation(env = e, n_timesteps = N_TIME)
s.simulate(N_SIM)
# Plot output
fig, ax = plt.subplots()
for var, c in zip(["x", "y"], ["#D55E00", "#009E73"]):
[ax.plot(pd.DataFrame(d)[var], label = var, color = c) for d in s.data_all]
ax.set_xlabel("Time")
ax.set_ylabel("Value")
plt.show()

Related

Move from one dimension to three-dimension

I've simple simulation setup which is generates one dimensional numpy.arrays using the np.random.normal distribution.
class Brownian_motion_Langevin:
def solve(self):
dB = self.sigma * np.random.normal(size=len(self.steps))
r2 = self.initial_y + np.cumsum(dB)
# Append solutions
self.values = r2
Now I need to change the solve function to return a three dimensional array. The easiest way I know is to rerun the code to get the three one-dimensional arrays, which is not very good! Does anyone suggest any effcient/smart method to implement the function into three dimensions?
Currently, the output of the code is (5, 10001), first element corresponds to number of times the simulation runs and second element is number of steps. What I expect is (5, 10001, 3), here third element is number of dimensions. Here is the complete reproducible code. Thanks!
#!/usr/bin/env python
#
# Python imports
import numpy as np
import h5py
class Brownian_motion_Langevin:
def solve(self):
dB = self.sigma * np.random.normal(size=len(self.steps))
r2 = self.initial_y + np.cumsum(dB)
# Append solutions
self.values = r2
def __init__(self, diffusion_coefficient, initial_y, simulation_time, delta_t):
"""
:param diffusion_coefficient:
:param initial_y: 1
:param delta_t: dt - change in time - size of each interval
:param simulation_time: total time for simulation
"""
# Initial parameters
self.diffusion_coefficient = diffusion_coefficient
self.initial_y = initial_y
# Define time
self.simulation_time = simulation_time
# Get dt
self.delta_t = delta_t
self.steps = np.arange(0, np.floor(self.simulation_time / self.delta_t) + 1)
self.times = self.steps * self.delta_t
# Speed up calculations
self.sigma = (2*self.diffusion_coefficient * self.delta_t)**0.5
# Simulate the diffusion process
self.solution = []
self.solve()
# Define parameters for the process
n = 5 # Number of simulations
Dc = 1 # Dc - Diffusion coefficient
y0 = 0 # y0 - starting point
tt = 1e2 # tt - total time for each simulation
dt = 0.01 # dt - integration time step
# Run simulations
motions = []
for i in range(0, n):
motions.append(Brownian_motion_Langevin(diffusion_coefficient=Dc,
initial_y=y0,
simulation_time=tt,
delta_t=dt))
values = np.array([m.values for m in motions])
print(values.shape) # this outputs the (5, 10001)
####
# FIXME make values 3-dimensional

Multi-start implementation with GEKKO

Could not find an implementation of a multi-start approach for the solution of nlp optimization problems with GEKKO. Here there is an example using the six-hump function as a case study. The six-hump function is difficult to optimize due to the presence of multiple local optima. The multi-start approach works well and increases the chances to solve optimisation problems globally when combined with robust derivative based solvers as the ones included in GEKKO.
import numpy as np
from gekko import GEKKO
import sobol_seq
# General definition of the problem
lb = np.array([-3.0, -2.0]) # lower bounds
ub = np.array([3.0, 2.0]) # upper bounds
n_dim = lb.shape[0] # number of dimensions
# matrix of initial values
multi_start = 10 # number of times the optimisation problem is to be solved
# Sobol
sobol_space = sobol_seq.i4_sobol_generate(n_dim, multi_start)
x_initial = lb + (ub-lb)*sobol_space # array containing the initial points
# Multi-start optimisation
localsol = [0]*multi_start # storage of local solutions
localval = np.zeros((multi_start))
for i in range(multi_start):
print('multi-start optimization, iteration =', i+1)
# definition of the problem class with GEKKO
m = GEKKO(remote=False)
m.options.SOLVER = 3
x = m.Array(m.Var, n_dim)
# definition of the initial values and bounds for the optimizer
for j in range(n_dim):
x[j].value = x_initial[i,j]
x[j].lower = lb[j]
x[j].upper = ub[j]
# Definition of the objective function
f = (4 - 2.1*x[0]**2 + (x[0]**4)/3)*x[0]**2 + x[0]*x[1] \
+ (-4 + 4*x[1]**2)*x[1]**2 # six-hump function
# Solving the problem
m.Obj(f)
m.solve(disp=False)
localval[i] = m.options.OBJFCNVAL
x_local = [x[j].value for j in range(n_dim)]
localsol[i] = np.array(x_local)
# selecting the best solution
minindex = np.argmin(localval)
x_opt = localsol[minindex]
f_opt = localval[minindex]
Thanks for posting the multi-start code. That is an interesting problem with a simple objective function. A contour plot shows the six local minima of the objective function.
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
# Variables at mesh points
x = np.arange(-3, 3, 0.05)
y = np.arange(-2, 2, 0.05)
X,Y = np.meshgrid(x, y)
obj=(4-2.1*X**2+(X**4)/3)*X**2+X*Y \
+(-4+4*Y**2)*Y**2 # six-hump function
# Create a contour plot
plt.figure()
# Weight contours
CS = plt.contour(X, Y, obj,np.linspace(-1,100,102))
plt.clabel(CS, inline=1, fontsize=8)
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
Gekko can run in parallel with multiple threads when searching for a global solution. The optimal solution is the orange dot.
import numpy as np
import threading
import time, random
from gekko import GEKKO
import sobol_seq
class ThreadClass(threading.Thread):
def __init__(self, id, xi, yi):
s = self
s.id = id
s.m = GEKKO(remote=False)
s.x = xi
s.y = yi
s.objective = float('NaN')
# initialize variables
s.m.x = s.m.Var(xi,lb=-3,ub=3)
s.m.y = s.m.Var(yi,lb=-2,ub=2)
# Objective
s.m.Minimize((4-2.1*s.m.x**2+(s.m.x**4)/3)*s.m.x**2+s.m.x*s.m.y \
+ (-4+4*s.m.y**2)*s.m.y**2)
# Set global options
s.m.options.SOLVER = 3 # APOPT solver
threading.Thread.__init__(s)
def run(self):
# Don't overload server by executing all scripts at once
sleep_time = random.random()
time.sleep(sleep_time)
print('Running application ' + str(self.id) + '\n')
# Solve
self.m.solve(disp=False)
# Retrieve objective if successful
if (self.m.options.APPSTATUS==1):
self.objective = self.m.options.objfcnval
else:
self.objective = float('NaN')
self.m.cleanup()
# General definition of the problem
lb = np.array([-3.0, -2.0]) # lower bounds
ub = np.array([3.0, 2.0]) # upper bounds
n_dim = lb.shape[0] # number of dimensions
# matrix of initial values
multi_start = 10 # number of times the optimisation problem is to be solved
# Sobol
sobol_space = sobol_seq.i4_sobol_generate(n_dim, multi_start)
x_initial = lb + (ub-lb)*sobol_space # array containing the initial points
# Array of threads
threads = []
# Calculate objective for each initial value
id = 0
for i in range(multi_start):
# Create new thread
threads.append(ThreadClass(id, x_initial[i,0], x_initial[i,1]))
# Increment ID
id += 1
# Run applications simultaneously as multiple threads
# Max number of threads to run at once
max_threads = 8
for t in threads:
while (threading.activeCount()>max_threads):
# check for additional threads every 0.01 sec
time.sleep(0.01)
# start the thread
t.start()
# Check for completion
mt = 3.0 # max time
it = 0.0 # incrementing time
st = 1.0 # sleep time
while (threading.activeCount()>=1):
time.sleep(st)
it = it + st
print('Active Threads: ' + str(threading.activeCount()))
# Terminate after max time
if (it>=mt):
break
# Wait for all threads to complete
#for t in threads:
# t.join()
#print('Threads complete')
# Initialize array for objective
obj = np.empty(multi_start)
xs = np.empty_like(obj)
ys = np.empty_like(obj)
# Retrieve objective results
id = 0
for i in range(multi_start):
xs[i] = threads[id].m.x.value[0]
ys[i] = threads[id].m.y.value[0]
obj[i] = threads[id].objective
id += 1
print('Objective',obj)
print('x',xs)
print('y',ys)
best = np.argmin(obj)
print('Lowest Objective',best)
print('Best Objective',obj[best])
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
# Variables at mesh points
x = np.arange(-3, 3, 0.05)
y = np.arange(-2, 2, 0.05)
X,Y = np.meshgrid(x, y)
obj=(4-2.1*X**2+(X**4)/3)*X**2+X*Y \
+(-4+4*Y**2)*Y**2 # six-hump function
# Create a contour plot
plt.figure()
# Weight contours
CS = plt.contour(X, Y, obj,np.linspace(-1,100,102))
plt.plot(xs,ys,'rx')
plt.plot(xs[best],ys[best],color='orange',marker='o',alpha=0.7)
plt.clabel(CS, inline=1, fontsize=8)
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
In additional to finding all six local optimal values, one of the solutions is at a local maximum at (0,0). This can happen when local solvers are tasked with solving a global optimum. Here is the solution:
Objective [-1.80886276e-35 -2.15463824e-01 -2.15463824e-01 -2.15463824e-01
2.10425031e+00 -1.03162845e+00 -2.15463824e-01 2.10425031e+00
-1.03162845e+00 -2.15463824e-01]
x [-1.32794585e-19 1.70360672e+00 -1.70360672e+00 1.70360672e+00
1.60710475e+00 8.98420119e-02 -1.70360672e+00 -1.60710477e+00
-8.98420136e-02 1.70360671e+00]
y [ 2.11414394e-18 -7.96083565e-01 7.96083565e-01 -7.96083569e-01
5.68651455e-01 -7.12656403e-01 7.96083569e-01 -5.68651452e-01
7.12656407e-01 -7.96083568e-01]
Lowest Objective 5
Best Objective -1.0316284535

Matplotlib FuncAnimation not displaying any frames until animation is complete

I'm trying to animate a plot using matplotlib's FuncAnimation, however no frames of the animation are visible until the animation reaches the final frame. If I set repeat = True nothing is ever displayed. When I first run the code a matplotlib icon appears but nothing displays when I click on it until it shows me the final frame:
If I save the animation I see the animation display correctly so this leads me to think that my code is mostly correct so I hope this is a simple fix that I'm just missing.
Apologies if I'm dumping too much code but I'm not sure if there's anything that's not needed for the minimum reproducible example.
Here's the main code
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from quantum_custom.constants import spin_down, spin_up, H00, H11, H
import quantum_custom.walk as walk
class QuantumState:
def __init__(self, state):
self.state = state
#"coin flips"
max_N = 100 #this will be the final number of coin flips
positions = 2*max_N + 1
#initial conditions
initial_spin = spin_down
initial_position = np.zeros(positions)
initial_position[max_N] = 1
initial_state = np.kron(np.matmul(H, initial_spin), initial_position) #initial state is Hadamard acting on intial state, tensor product with the initial position
quantum_state = QuantumState(initial_state)
#plot the graph
fig, ax = plt.subplots()
plt.title("N = 0")
x = np.arange(positions)
line, = ax.plot([],[])
loc = range(0, positions, positions // 10)
plt.xticks(loc)
plt.xlim(0, positions)
plt.ylim((0, 1))
ax.set_xticklabels(range(-max_N, max_N + 1, positions // 10))
ax.set_xlabel("x")
ax.set_ylabel("Probability")
def init():
line.set_data([],[])
return line,
def update(N):
next_state = walk.flip_once(quantum_state.state, max_N)
probs = walk.get_prob(next_state, max_N)
quantum_state.state = next_state
start_index = N % 2 + 1
cleaned_probs = probs[start_index::2]
cleaned_x = x[start_index::2]
line.set_data(cleaned_x, cleaned_probs)
if cleaned_probs.max() != 0:
plt.ylim((0, cleaned_probs.max()))
plt.title(f"N = {N}")
return line,
anim = animation.FuncAnimation(
fig,
update,
frames = max_N + 1,
init_func = init,
interval = 20,
repeat = False,
blit = True,
)
anim.save("animated.gif", writer = "ffmpeg", fps = 15)
plt.show()
Here's my quantum_custom.constants module.
#define spin up and spin down vectors as standard basis
spin_up = np.array([1,0])
spin_down = np.array([0,1])
#define our Hadamard operator, H, in terms of ith, jth entries, Hij
H00 = np.outer(spin_up, spin_up)
H01 = np.outer(spin_up, spin_down)
H10 = np.outer(spin_down, spin_up)
H11 = np.outer(spin_down, spin_down)
H = (H00 + H01 + H10 - H11)/np.sqrt(2.0) #matrix representation of Hadamard gate in standard basis
Here's my quantum_custom.walk module.
import numpy as np
from quantum_custom.constants import H00, H11, H
#define walk operators
def walk_operator(max_N):
position_count = 2 * max_N + 1
shift_plus = np.roll(np.eye(position_count), 1, axis = 0)
shift_minus = np.roll(np.eye(position_count), -1, axis = 0)
step_operator = np.kron(H00, shift_plus) + np.kron(H11, shift_minus)
return step_operator.dot(np.kron(H, np.eye(position_count)))
def flip_once(state, max_N):
"""
Flips the Hadamard coin once and acts on the given state appropriately.
Returns the state after the Hadamard coin flip.
"""
walk_op = walk_operator(max_N)
next_state = walk_op.dot(state)
return next_state
def get_prob(state, max_N):
"""
For the given state, calculates the probability of being in any possible position.
Returns an array of probabilities.
"""
position_count = 2 * max_N + 1
prob = np.empty(position_count)
for k in range(position_count):
posn = np.zeros(position_count)
posn[k] = 1
posn_outer = np.outer(posn, posn)
alt_measurement_k = eye_kron(2, posn_outer)
proj = alt_measurement_k.dot(state)
prob[k] = proj.dot(proj.conjugate()).real
return prob
def eye_kron(eye_dim, mat):
"""
Speeds up the calculation of the tensor product of an identity matrix of dimension eye_dim with a given matrix.
This exploits the fact that majority of values in the resulting matrix will be zeroes apart from on the leading diagonal where we simply have copies of the given matrix.
Returns a matrix.
"""
mat_dim = len(mat)
result_dim = eye_dim * mat_dim #dimension of the resulting matrix
result = np.zeros((result_dim, result_dim))
result[0:mat_dim, 0:mat_dim] = mat
result[mat_dim:result_dim, mat_dim:result_dim] = mat
return result
I know that saving the animation is a solution but I'd really like to have the plot display just from running the code as opposed to having to save it. Thanks!
As per Sameeresque's suggestion I tried using different backends for matplot lib. This was done by altering by import statements as follows.
import matplotlib
matplotlib.use("tkagg")
import matplotlib.pyplot as plt
Note that it's important to add the two additional lines before import matplotlib.pyplot as plt otherwise it won't do anything.

Monte Carlo Simulation with triangular and normal probability density distibution using numpy

I am trying to perform a Monte Carlo Simulation in order to calculate the uncertainty in the electricity costs of a heat pump system. I have several input parameters (COPs, electricity costs), which are of a triangular probability distribution. The total electricity costs are composed of the sum of the calculated costs of the three subcomponents (heatpump and pumps) and are of an (approximately) normal probability distribution.
I was wondering if I am performing the MC simulation correctly. Since I have to loop this MC simulation over 70 different heat pump systems, I am also wondering if there is a faster method.
As I am an absolute greenhorn in coding, please apologize for my messy code.
I am thankful for any help!
My code:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from numpy.random import triangular
N = 1_000_000
def energy_output(coef_performance, energy_input):
return energy_input * coef_performance / (coef_performance - 1)
COP_DISTRIBUTION_PARAM = dict(left=4, mode=4.5, right=5)
def seed_cop():
return triangular(**COP_DISTRIBUTION_PARAM )
INPUT_ENERGY_HEATING = 866
INPUT_ENERGY_COOLING = 912
def random_energy_output():
return energy_output(seed_cop(), energy_input=INPUT_ENERGY_HEATING)
energy_outputs = [random_energy_output() for _ in range(N)]
a = min(energy_outputs)
b = max(energy_outputs)
med = np.median(energy_outputs)
############################
def elec_costs_heatpump(elec_costs, coef_performance,energy_output):
return energy_output * 1000 / coef_performance * elec_costs
ELEC_DISTRIBUTION_PARAM = dict(left=0.14, mode=0.15, right=0.16)
def seed_elec():
return triangular(**ELEC_DISTRIBUTION_PARAM )
HP_OUTPUT_DISTRIBUTION_PARAM = dict(left=a, mode=med, right=b)
def seed_output():
return triangular(**HP_OUTPUT_DISTRIBUTION_PARAM )
def random_elec_costs_heatpump():
return elec_costs_heatpump(seed_elec(),seed_cop(),seed_output() )
elec_costs_heatpump = [random_elec_costs_heatpump() for _ in range(N)]
mean_hp = np.mean(elec_costs_heatpump)
std_hp = np.std(elec_costs_heatpump)
############################
def elec_costs_coldpump(elec_costs, coef_performance_pump,energy_input):
return energy_input * 1000 / coef_performance_pump * elec_costs
COP_PUMP_DISTRIBUTION_PARAM = dict(left=35, mode=40, right=45)
def seed_cop_pump():
return triangular(**COP_PUMP_DISTRIBUTION_PARAM )
def random_elec_costs_coldpump():
return elec_costs_coldpump(seed_elec(),seed_cop_pump(), energy_input=INPUT_ENERGY_COOLING)
elec_costs_coldpump = [random_elec_costs_coldpump() for _ in range(N)]
mean_cp = np.mean(elec_costs_coldpump)
sdt_cp = np.std(elec_costs_coldpump)
#########################
def elec_costs_warmpump(elec_costs, coef_performance_pump,energy_input):
return energy_input * 1000 / coef_performance_pump * elec_costs
def random_elec_costs_warmpump():
return elec_costs_warmpump(seed_elec(),seed_cop_pump(), energy_input=INPUT_ENERGY_HEATING)
elec_costs_warmpump = [random_elec_costs_warmpump() for _ in range(N)]
mean_wp = np.mean(elec_costs_warmpump)
sdt_wp = np.std(elec_costs_warmpump)
#########################
def total_costs(costs_heatpump, costs_coldpump, costs_warmpump):
return costs_heatpump + costs_coldpump + costs_warmpump
ELEC_COSTS_HEATPUMP_PARAM = dict(loc=mean_hp, scale=sdt_hp)
def seed_costs_hp():
return np.random.normal(**ELEC_COSTS_HEATPUMP_PARAM )
ELEC_COSTS_COLDPUMP_PARAM = dict(loc=mean_cp, scale=sdt_cp)
def seed_costs_cp():
return np.random.normal(**ELEC_COSTS_COLDPUMP_PARAM )
ELEC_COSTS_WARMPUMP_PARAM = dict(loc=mean_wp,scale=sdt_wp)
def seed_cost_wp():
return np.random.normal(**ELEC_COSTS_WARMPUMP_PARAM )
def random_total_costs():
return seed_costs_hp(), seed_costs_cp(), seed_cost_wp()
total_costs = [random_total_costs() for _ in range(N)]
print(total_costs)
#Plot = plt.hist(total_costs, bins=75, density=True)
Great you have a prototype for your code!
Some impressions on code structure and readability:
the quickest improvement is separating functions and scripting part, this allows to split your algorithm into simple testable blocks and keep control of calculations in one place, followed by plotting
some repeated code can go to own functions
it pays back to stick to more widely accepted naming convention (PEP8), this way people are less astonished less with style and can devote more attention to code substance. Specifically, you normally name functions lowercase underscore def do_something(): and UPPERCASE is reserved for constants.
Need to be able to run your code to check for speedups, see comments above about what is lacking.
Update on more complete code in question, some additional comments:
let functions be functions, do not pass the function arguments as globals
consider separating your model structure (your equations) from parameters (fixed inputs and seed values), it pays back in longer run
avoid 'magic numbers', put them into constants (example is 866) or pass explicitly as args.
Here is a refactoring to consider:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from numpy.random import triangular
N = 1_000_000
#def EnergyHP():
# COP_Hp = triangular(4.0, 4.5, 5) # COP of heatpump
# Ein = 866 # Energy input heatpump
# return Ein * COP_Hp /(COP_Hp-1) # Calculation of Energy output of heatpump
#
#Eout = np.zeros(N)
#for i in range(N):
# Eout[i] = EnergyHP()
#minval = np.min(Eout[np.nonzero(Eout)])
#maxval = np.max(Eout[np.nonzero(Eout)])
#Medi= np.median(Eout, axis=0)
def energy_output(coef_performance, energy_input):
"""Return energy output of heatpump given efficiency and input.
Args:
coef_performance - <description, units of measurement>
energy_input - <description, units of measurement>
"""
return energy_input * coef_performance / (coef_performance - 1)
# you will use this variable again, so we put it into a function to recylce
COP_DISTRIBUTION_PARAM = dict(left=4, mode=4.5, right=5)
def seed_cop():
"""Get random value for coef of performance."""
return triangular(**COP_DISTRIBUTION_PARAM )
# rename it if it is a constant, pass as an argument is it is not
SOME_MEANINGFUL_CONSTANT = 866
def random_energy_output():
return energy_output(seed_cop(), energy_input=SOME_MEANINGFUL_CONSTANT)
# pure python list and metrics
energy_outputs = [random_energy_output() for _ in range(N)]
a = min(energy_outputs)
b = max(energy_outputs)
med = np.median(energy_outputs)
# above does this does not use numpy, but you can convert it to vector
energy_outputs_np = np.array(energy_outputs)
# or you can construct np array directly, this is a very readable way
# make a vector or random arguments
cop_seeded = triangular(**COP_DISTRIBUTION_PARAM, size=N)
# apply function
energy_outputs_np_2 = energy_output(cop_seeded, SOME_MEANINGFUL_CONSTANT)
The next pressing intent is writing a HeatPump class, but I suggest sticking
to functions as long as you can - that usually makes our thinking about a class
state and methods better.
I also appears the seeded values for parameters may not be independent, in some experiment you can draw them from a joint distribution.

passing a function as an argument to a class

I have a function is given by :
import scipy.special
def p(z):
z0=1./3.;eta=1.0
value=eta*(z**2)*numpy.exp(-1*(z/z0)**eta)/scipy.special.gamma(3./eta)/z0**3
return value
I want to pass this function to the following class which is in the file called redshift_probability.py as an argument p:
import pylab
import numpy
import pylab
import numpy
class GeneralRandom:
"""This class enables us to generate random numbers with an arbitrary
distribution."""
def __init__(self, x = pylab.arange(-1.0, 1.0, .01), p = None, Nrl = 1000):
"""Initialize the lookup table (with default values if necessary)
Inputs:
x = random number values
p = probability density profile at that point
Nrl = number of reverse look up values between 0 and 1"""
if p == None:
p = pylab.exp(-10*x**2.0)
self.set_pdf(x, p, Nrl)
def set_pdf(self, x, p, Nrl = 1000):
"""Generate the lookup tables.
x is the value of the random variate
pdf is its probability density
cdf is the cumulative pdf
inversecdf is the inverse look up table
"""
self.x = x
self.pdf = p/p.sum() #normalize it
self.cdf = self.pdf.cumsum()
self.inversecdfbins = Nrl
self.Nrl = Nrl
y = pylab.arange(Nrl)/float(Nrl)
delta = 1.0/Nrl
self.inversecdf = pylab.zeros(Nrl)
self.inversecdf[0] = self.x[0]
cdf_idx = 0
for n in xrange(1,self.inversecdfbins):
while self.cdf[cdf_idx] < y[n] and cdf_idx < Nrl:
cdf_idx += 1
self.inversecdf[n] = self.x[cdf_idx-1] + (self.x[cdf_idx] - self.x[cdf_idx-1]) * (y[n] - self.cdf[cdf_idx-1])/(self.cdf[cdf_idx] - self.cdf[cdf_idx-1])
if cdf_idx >= Nrl:
break
self.delta_inversecdf = pylab.concatenate((pylab.diff(self.inversecdf), [0]))
def random(self, N = 1000):
"""Give us N random numbers with the requested distribution"""
idx_f = numpy.random.uniform(size = N, high = self.Nrl-1)
idx = pylab.array([idx_f],'i')
y = self.inversecdf[idx] + (idx_f - idx)*self.delta_inversecdf[idx]
return y
I don't know how to pass input argument x as an input parameter to function p(z) when I call the class
from redshift_probability import GeneralRandom
z_pdf=GeneralRandom()
If I do as following I get error:
z_pdf.set_pdf( x=numpy.arange(0, 1.5, .001),p(x),N=1000000)
How do I modify it?
I think you want to change GeneralRandom.__init__ to look like this:
def __init__(self, x = pylab.arange(-1.0, 1.0, .01), p_func=None, Nrl = 1000):
"""Initialize the lookup table (with default values if necessary)
Inputs:
x = random number values
p_func = function to compute probability density profile at that point
Nrl = number of reverse look up values between 0 and 1"""
if p_func is None:
self.p_val = pylab.exp(-10*x**2.0)
else:
self.p_val = p_func(x)
Then call it like this:
GeneralRandom(p_func=p)
That way, if you provide p_func it will be called with x as an argument, but if it's not provided, it gets set the same default as before. There's no need to call set_pdf explicitly, because it's called at the end of __init__.

Categories