Optimering i Python med E-serier

C, C++, Pascal, Assembly, Raspberry, Java, Matlab, Python, BASIC, SQL, PHP, etc.
Användarvisningsbild
psynoise
EF Sponsor
Inlägg: 7154
Blev medlem: 26 juni 2003, 19:23:36
Ort: Landvetter

Optimering i Python med E-serier

Inlägg av psynoise »

Försöker få till ett verktyg i Python för design av enklare kretsar så som spänningsdelare med mera utifrån komponentvärden enligt specificerad serie.

Vartannat E48-värde och lägre serier är enkelt då jag kan beräkna alla kombinationer och sedan ta ut de kombinationer som ger minst fel. Men för större serier (E192) och kretsar med många komponenter räcker inte minnet till då antalet kombinationer blir väldigt många.

Därav söker jag en funktion eller klass som kan ta ett matematiskt uttryck (objekt) och villkor/restriktion (komponentvärden/serier) som optimerar mot vilka kombinationer som ger minst fel mot önskat resultat.

Tyvärr är jag nybörjare med Python och programmering gör jag väldigt sällan varav jag hellre använder något enkelt än resurseffektivt.

Har ni andra några idéer över Python-verktyg som skulle kunna hjälpa mig eller någon algoritm som går enkelt att implementera?
Användarvisningsbild
arvidb
Inlägg: 4537
Blev medlem: 8 maj 2004, 12:56:24
Ort: Stockholm

Re: Optimering i Python med E-serier

Inlägg av arvidb »

Varför spara resultaten av alla beräkningar? Du är väl bara intresserad av den kombination som är bäst?

Beräkna alltså en kombination i taget. Om resultatet är bättre än det bästa du har hittat hittills så sparar du informationen om den bättre kombinationen och kasserar det tidigare resultatet. Är resultatet sämre så går du bara vidare utan att spara någonting.
Användarvisningsbild
psynoise
EF Sponsor
Inlägg: 7154
Blev medlem: 26 juni 2003, 19:23:36
Ort: Landvetter

Re: Optimering i Python med E-serier

Inlägg av psynoise »

Tack, det ska jag såklart testa. Snöade in på numpy och då blev mina beräkningar mer matris/Matlab-inspirerade men så behöver det såklart inte vara. Sedan spelar det inte någon större roll om optimeringen skulle ta lång tid.
Användarvisningsbild
psynoise
EF Sponsor
Inlägg: 7154
Blev medlem: 26 juni 2003, 19:23:36
Ort: Landvetter

Re: Optimering i Python med E-serier

Inlägg av psynoise »

Nu har jag knåpat ihop något som jag tror ska fungera. För att få upp hastigheten omvandlas sympy uttryck till en vanlig funktion.

Kod: Markera allt

# -*- coding: utf-8 -*-
"""
Add description!
"""
import sympy as sp

class BestCombinations():
    
    def __init__(self, expression, variables, series, goal):
        print("In init of model/BestCombinations")
        print(f"  Number of variables: {len(variables)}")
        print(f"  Number of series: {len(series)}")

        y = sp.lambdify(variables, expression)
        min_error = float("inf")
        
        
        if len(variables) == 2:
            x1, x2 = series
            print(f"  len(x1) = {len(x1)}")
            print(f"  len(x2) = {len(x2)}")
            
            n1 = 0
            while n1 <  len(x1):
                n2 = 0
                while n2 <  len(x2):
                    error = abs(goal - y(x1[n1], x2[n2]))
                    if error < min_error:
                        min_error = error
                        self.results = [x1[n1], x2[n2]]
                        print(f"Run {(n1+1)*(n2+1)} of {len(x1)*len(x2)}")
                        print(f"  {self.results}")
                        print(f"  min_error = {min_error}")
                    elif error == min_error:
                        self.results = self.results, [x1[n1], x2[n2]]
                        print(f"Run {(n1+1)*(n2+1)} of {len(x1)*len(x2)}")
                        print(f"  {self.results}")
                        print(f"    min_error = {min_error}")
                    n2 += 1
                n1 += 1
            
        else:
            print("ERROR in init of model/BestCombinations")

Kod: Markera allt

# -*- coding: utf-8 -*-
"""
Add description!
"""
import sympy as sp
import numpy as np
from model.bestcombinations import BestCombinations

"""
Series connection with two resistors
"""

R, R1, R2 = sp.symbols('R R1 R2')
R = R1 + R2

ser1 = np.array([0, 1, 1.5, 2.2, 3.3, 4.7, 6.8])
ser1 = np.append(ser1, 10*ser1)
ser1 = np.append(ser1, 100*ser1)

ser2 = np.array([0, 1, 2.2, 4.7])
ser2 = np.append(ser2, 10*ser2)
ser2 = np.append(ser2, 100*ser2)

seriesResistorsCombinations =  BestCombinations(
    R, [R1, R2], [ser1, ser2], 56)

print(f"""seriesResistorsCombinations.results = 
      {seriesResistorsCombinations.results}""")
mounte
Inlägg: 204
Blev medlem: 14 november 2010, 13:15:00
Ort: Sandviken

Re: Optimering i Python med E-serier

Inlägg av mounte »

Bra att du fått ihop en lösning.
Passar på att lämna lite "lösa" kommentarer.

Det är god praxis att i huvudprogrammet wrappa alla kod i en metod, kalla den vad du vill och sedan använda:

Kod: Markera allt

if __name__ == "__main__":
    det_namn_du_satte()
Anledningen är att alla som använder din python-modul och importerar den filen automagiskt kommer få sidoeffekten att alla kod faktiskt exekveras. Vilket kanske inte är önskvärt.

Vidare så fundera noga om du verkligen behöver wrappa din modell i en klass.
I python är det lätt gjort och ofta landar man i att man skapar "fusk-objekt" mest beståendes av metoder.
Använder man klasser så ska det finnas en poäng med det och inte bara rakt av exekvera en massa kod i initieringen av en klass.
I detta exempel (som mycket väl kan vara väldigt kondenserat) så skulle jag starkt överväga att bara ha en metod likt:

Kod: Markera allt

def find_optimal_component_values(expression, variables, series, goal):
  ...
När jag ändå nosar på moduler, klasser etc. så är python trevlig då det finns så mycket färdigt att använda. Man bör överväga vad "kostnaden" är att ta in en dependency (storlek, maintenance, uppdateringar etc.) mot nyttan.
Just i det exempel du postar så förstår jag att du började från en numpy-lösning som var minnesintensiv till att sedan göra en mer iterativ lösning. Vidare har du använt sympy för att på ett smidigt sätt uttrycka ekvationer. Dessa använder ca 14MB respektive 6MB och de används tämligen sparsmakat. Jag hade övervägt att klara mig utan dessa.
När du jobbar med enklare typer av formler så kan du enkelt definiera dom som vanliga python-metoder (eller lambda-metoder).

En till punkt som man kan disktuera är iterationer i python. Ofta när man ser.

Kod: Markera allt

for i in range(len(some_list)):
    do_stuff(some_list[i])
eller

Kod: Markera allt

i = 0
while i < len(range(some_list)):
    do_stuff(some_list[i])
    i += 1
så är det i 99 fall av 100 pga vilken bakrund man har. Det är (nästan) alltid renare och enklare att följa med då man låter python få iterera över objekt likt:

Kod: Markera allt

for value in some_list:
    do_stuff(value)
om man behöver ha ut ett index samtidigt för status eller vad som helst så överväg att istället använda enumerate() på iteratorn:

Kod: Markera allt

for index, value in enumerate(some_list):
    print(f"working on {index}")
    do_stuff(value)
Så till sist en hastigt omskriven version (utan utskrifter etc. för att fokusera mer på koden

Kod: Markera allt

from functools import reduce
from itertools import product

def calculate_best(expression, series: list, goal: float):
    min_error = float("inf")
    results = set()
    
    if len(series) != 2:
        print("ERROR in init of model/BestCombinations")
        return
    
    for x1, x2 in product(*series):
        error = abs(goal - expression(x1, x2))
        if x2 < x1:
            x1, x2 = x2, x1
        if error < min_error:
            min_error = error
            results = {(x1, x2)}
        elif error == min_error:
            results.add((x1, x2))

    return results
        

def main():
    def expression(x1: float, x2: float):
        return x1+x2

    def add_multiples(s: set, n: float):
        s.update(list(map(lambda v: v*n, ser1)))    

    ser1 = set((0, 1, 1.5, 2.2, 3.3, 4.7, 6.8))
    add_multiples(ser1, 10)
    add_multiples(ser1, 100)

    ser2 = set((0, 1, 2.2, 4.7))
    add_multiples(ser2, 10)
    add_multiples(ser2, 100)

    result = calculate_best(expression, [ser1, ser2], 56)

    print(f"{result=}")

if __name__ == "__main__":
    main()

Hoppas du fortsatt gillar python och att du fortsätter att lösa spännande problem :)

PS. problemet du löser är en variant på "Coin Change Problem" som man klassiskt löser med dynamisk programmering. En fördel med att göra denna uttömmande sökning är att du hittar alla lösningar och att du kan knyta vikter/kostnader till varje motstånd och för varje lösning räkna ut den "bästa" (den som använder billigast motstånd, den som använder motstånd som finns på lager, den som använder motstånd som det finns gott om på lager, etc.... möjligheterna är "oändliga").
DS
Skriv svar