динамическое заполнение словарей в Python

У меня есть самодельный алгоритм имитации отжига для расчета минимальной энергии системы с N количеством точек. Энергия между двумя точками рассчитывается как 1/r, где r — расстояние между двумя точками. Я запускаю свой код в цикле, и как только я выхожу из цикла, я обновляю новую запись в словаре. Проблема в том, что при последнем запуске цикла компьютер, кажется, сохраняет последнюю запись как все записи. См. код ниже:

def energy_find(number,Ts=T_s,Tf=T_f):
    val_dic = {}
    c = 0 
    radius,theta = generate_random(number)
    energy,matrix = total_energy(number,radius,theta)
    val_dic[0] = []
    val_dic[0] = {"radius":radius,"theta":theta,"energy":energy,"energies":matrix}
    m = 5 # number of repetitions per given temperature
    for i in range(m):
        c +=1
        old_theta = val_dic[c-1]["theta"]
        old_radius = val_dic[c-1]["radius"]
        energy= val_dic[c-1]["energy"]
        old_energies = val_dic[c-1]["energies"]
        new_theta,new_radius,which = moveCharge(number,old_theta,old_radius)
        new_energy,enMatrix= recalculate(number,new_radius,new_theta,old_energies,which)
        delta_energy = new_energy-energy
        newset = [new_radius,new_theta,new_energy,enMatrix]
        val_dic = acceptChange(newset,delta_energy,val_dic,c,Ts)        
        print(val_dic[c]['radius'])
    df = pd.DataFrame(val_dic).T
    energy = df.energy.min()
    index =  pd.to_numeric(df.energy).idxmin()
    theta = df.loc[index,"theta"]
    radius = df.loc[index,"radius"]

    return df,energy,radius,theta,delta,val_dic

Как видите, выше приведен оператор pring, который корректно выводит изменение радиального положения точечных зарядов. Однако после запуска функции:

df,energy,radius,theta,delta,dic= energy_find(5)
print("stop")
print(dic[1]["radius"])

И вывод:

[3, 4.95, 6, 9, 2]
[5.05, 4.95, 6, 9, 2]
[3.0, 4.95, 6, 9, 2]
[3.0, 4.95, 6, 9, 4.05]
[5.05, 4.95, 6, 9, 4.05]
stop
[5.05, 4.95, 6, 9, 4.05]

за :

print(dic[2]["radius"]) 

Выход:

[5.05, 4.95, 6, 9, 4.05]

Точно так же, как для dic[1], и равно последнему значению, напечатанному в цикле for во время работы функции. Я неправильно использую словари?

В случае, если они необходимы: это функция, которую я использую в коде:

def uniform(n):
    global _first_random
    if n==0: random.seed(1234); _first_random=0
    if _first_random==1: random.seed(None); _first_random=0
    if n==1: return random.random()
    else: return floor(n*random.random()+1)

_first_random=1
r = 10

def generate_random(number):
    """
    Function for creating random position for n charges

    Parameters
    ---------
    number -- number of charges in the system. Takes integer values.

    Outputs 
    --------
    charges_radius,
    charges_theta

    """
    charges_radius = []
    charges_theta = []
    for i in range(number):
        radius = randrange(r)
        theta= np.random.random() * 2.0 * np.pi
        while theta in charges_theta and radius in charges_radius or radius==0 and radius in charges_radius:
            radius = uniform(10)
            theta= np.random.random() * 2.0 * np.pi
        charges_radius.append(radius)
        charges_theta.append(theta)
    return charges_radius,charges_theta



def cosRule(rad1,rad2,ang1,ang2):
    q = 1.0
    net= ang2-ang1
    net_distance = sqrt(rad1**2+rad2**2-2*rad1*rad2*cos(net))
    try:
        energy = q*q*(1.0/net_distance)

    except ZeroDivisionError:
        energy = 1e12
    return energy

def partial_energy(no,radii,thetas,enMatrix):
    """
    no- ordinary number of the charge that you moved and calculate the change in energy as a result of displacement" 
    """
    radiusA = radii[no]
    thetaA  = thetas[no]
    for key,theta in enumerate(thetas):
        if key!=no:
            radiusB = radii[key]
            thetaB = theta
            energy = cosRule(radiusB,radiusA,thetaB,thetaA)
            enMatrix[key][no]= 0.5*energy
            enMatrix[no][key] = enMatrix[key][no]
    return enMatrix

def total_energy(n,radius,thetas):
    enMatrix = np.zeros([n,n])
    energy = None
    for i in range(n):
        enMatrixNew= partial_energy(i,radius,thetas,enMatrix=enMatrix)
        energy = sum(enMatrixNew).sum()
    return energy,enMatrix

def recalculate(n,radius,thetas,enMatrix,which):
    enMatrixNew = np.zeros([n,n])
    enMatrixNew=partial_energy(which,radius,thetas,enMatrix)
    energy = sum(enMatrixNew).sum()
    return energy,enMatrixNew



def tempScaling(a):
    T_s = -a/log(0.7) 
    T_f = -a/log(0.01) 
    return T_s, T_f


T_s, T_f = tempScaling(0.2)


def moveCharge(number,thetas,radius):
    r = 10
    step = 2.05
    which = randrange(number)
    thetas[which] =2*uniform(1)*np.pi
    n = randrange(1,3)
    delta_radius = (-1)**n *step
    radius[which] +=delta_radius
    if radius[which]>r or radius[which]<0.0:
        radius[which] +=(-1)**(n+1) * step
    return thetas,radius,which


def acceptChange(newset,delta,val_dic,c,Ts):
    if delta >0.0:
        accept_the_change = uniform(1) # generating a random number to decide if we accept the change
        if accept_the_change < exp(-delta/Ts):
            val_dic[c]=[]
            val_dic[c]={"radius":newset[0],"theta":newset[1],"energy":newset[2],"energies":newset[3]}   

        else:
            val_dic[c]=[]
            val_dic[c]=val_dic[c-1]

    else:
        val_dic[c]=[]
        val_dic[c]={"radius":newset[0],"theta":newset[1],"energy":newset[2],"energies":newset[3]} 
    return val_dic    

person kikatuso    schedule 10.03.2020    source источник


Ответы (1)


Я думаю, проблема в том, что когда вы пишете что-то вроде:

old_theta = val_dic[c-1]["theta"]

Вы не копируете предыдущую тету, вы заставляете old_theta ссылаться на тот же объект, и если вы затем измените old_theta, он также изменит val_dic[c-1]["theta"].

Вы можете использовать модуль копирования, чтобы избежать проблемы и изменить строку, как показано ниже:

import copy 

old_theta = copy.copy(val_dic[c-1]["theta"])
old_radius = copy.copy(val_dic[c-1]["radius"])
energy= copy.copy(val_dic[c-1]["energy"])
old_energies = copy.copy(val_dic[c-1]["energies"])

Тогда old_radius будет просто значением предыдущего радиуса, а не того же объекта.

person ThomaS    schedule 10.03.2020
comment
Да, это была проблема!!! Спасибо, вы спасли мне жизнь (и мое задание :D) - person kikatuso; 10.03.2020