Mercurial > ~darius > hgwebdir.cgi > pa
view sim.py @ 20:3341ef03cb66
Add PWM modulator
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Mon, 27 Nov 2023 13:19:37 +1030 |
parents | 28475b505f1f |
children |
line wrap: on
line source
#!/usr/bin/env python3 # Copyright (c) 2023 Daniel O'Connor <darius@dons.net.au # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. import datetime import pathlib from scipy.optimize import differential_evolution from spicelib.simulators.ltspice_simulator import LTspice from spicelib.log.ltsteps import LTSpiceLogReader from spicelib.editor.spice_editor import SpiceEditor #from spicelib.sim.sim_runner import SimRunner import shlex import sqlite3 import subprocess def simulate(src, simexe, simflags, rundir, runname, params): src = pathlib.Path(src) runname = pathlib.Path(runname) assert(runname.suffix == '.net') rundir = pathlib.Path(rundir) e = SpiceEditor(src) for k in params: e.set_parameter(k, params[k]) #s = SimRunner(simulator = LTspice, output_folder = 'tmp', verbose = True) #s.simulator.spice_exe = [simexe] #raw_file, log_file = s.run_now(e, run_filename = runname) e.write_netlist(rundir / runname) cmd = [simexe, '-b'] cmd.extend(simflags) cmd.append(runname.name) then = datetime.datetime.now() #print(f'Starting run {runname} at {then}') p = subprocess.Popen(cmd, cwd = rundir) p.communicate() now = datetime.datetime.now() taken = now - then if p.returncode != 0: raise Exception(' '.join(map(shlex.quote, cmd)) + ' failed with code ' + str(p.returncode)) #rawpath = rundir / runname.with_suffix('.raw') #raw = spicelib.RawRead(rawpath) logpath = rundir / runname.with_suffix('.log') log = LTSpiceLogReader(logpath) power = log.get_measure_value('pout') eff = log.get_measure_value('efficiency') thd = log.fourier['V(rfout)'][0].thd ipeak_u2 = log.get_measure_value('ipeak_u2') ipeak_u5 = log.get_measure_value('ipeak_u5') ipeak = max(ipeak_u2, ipeak_u5) return then, taken, power, eff, thd, ipeak def calccost(power, eff, thd, ipeak): # Calculate the cost tpwr = 1500 tthd = 2 teff = 90 imax = 11 cost = 0 if power < tpwr * 0.80: cost += 100 elif power < tpwr: cost += (tpwr - power) / tpwr / 100 if thd > 5 * tthd: cost += 100 else: thdinv = 100 - thd tthdinv = 100 - tthd if thdinv < tthdinv: cost += (tthdinv - thdinv) / tthdinv if eff < teff: cost += (teff - eff) / teff if ipeak > imax: cost += (ipeak - imax) * 5 return cost def fn(v, dsn, simexe, simflags, rundir, circ): '''Called by differential_evolution, v contains the evolved parameters, the rest are passed in from the args parameter'. Returns the cost value which is being minimised''' # Get parameters for run duty, c1, c2, l1, l2 = v # Check if this combination has already been tried dbh = sqlite3.connect(dsn) cur = dbh.cursor() cur.execute('SELECT run, power, efficiency, thd, ipeak FROM GAN190 WHERE name = ? AND duty = ? AND c1 = ? AND c2 = ? AND l1 = ? AND l2 = ?', (circ, duty, c1, c2, l1, l2)) tmp = cur.fetchone() if tmp is not None: run, power, eff, thd, ipeak = tmp # Recalculate the cost since it is cheap and might have been tweaked since the original run cost = calccost(power, eff, thd, ipeak) print(f'Found run {run:3d}: Duty {duty:3.0f}%, C1 {c1:3.0f}pF, C2 {c2:3.0f}pF, L1 {l1:3.0f}uH, L2 {l2:4.0f}nH -> Power: {power:6.1f}W Efficiency: {eff:5.1f}% THD: {thd:5.1f}% IPeak: {ipeak:4.1f}A Cost: {cost:6.2f}') return cost # Get next run number cur.execute('BEGIN DEFERRED') cur.execute('INSERT INTO GAN190 DEFAULT VALUES RETURNING rowid') run = cur.fetchone()[0] cur.execute('COMMIT') # Run the simulation # Need to convert units to suit runname = pathlib.Path(circ).stem + '-' + str(run) + '.net' try: then, taken, power, eff, thd, ipeak = simulate(circ, simexe, simflags, rundir, runname, {'dutypct' : duty, 'C1' : c1 * 1e-12, 'C2' : c2 * 1e-12, 'L1' : l1 * 1e-6, 'L2' : l2 * 1e-9}) except: return 100000 # Calculate the cost cost = calccost(power, eff, thd, ipeak) # Log & save the results print(f'Run {run:3d}: Duty {duty:3.0f}%, C1 {c1:3.0f}pF, C2 {c2:3.0f}pF, L1 {l1:3.0f}uH, L2 {l2:4.0f}nH -> Power: {power:6.1f}W Efficiency: {eff:5.1f}% THD: {thd:5.1f}% IPeak: {ipeak:4.1f}A Cost: {cost:6.2f}') taken = taken.seconds + taken.microseconds / 1e6 cur.execute('BEGIN DEFERRED') cur.execute('REPLACE INTO GAN190 (name, run, start, duration, duty, c1, c2, l1, l2, power, efficiency, thd, ipeak, cost) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', (circ, run, then, taken, duty, c1, c2, l1, l2, power, eff, thd, ipeak, cost)) cur.execute('COMMIT') return cost def ev(circ): # Bounds for parameters # Note that the parameters are also constrained to be integral bounds = [(10, 80), (1, 20), (10, 300), (1, 20), (10, 500)] # Initial solution x0 = [36, 10, 155, 5, 140] # Where to save results dsn = 'results.db' dbh = sqlite3.connect(dsn) cur = dbh.cursor() cur.execute(''' CREATE TABLE IF NOT EXISTS GAN190 ( name TEXT, -- Circuit name run INTEGER, -- Run number start DATETIME, -- Datetime run started duration REAL, -- Length of run (seconds) duty REAL, -- Duty cyle (%) c1 REAL, -- Value of C1 (pF) c2 REAL, -- Value of C2 (pF) l1 REAL, -- Value of L1 (uH) l2 REAL, -- Value of L2 (nH) power REAL, -- Measured power (Watts) efficiency REAL, -- Measured efficiency (%) thd REAL, -- Total harmonic distortion (%) ipeak REAL, -- Peak drain current (A) cost REAL -- Calculated cost metric );''') return differential_evolution(fn, bounds, x0 = x0, args = (dsn, '/Users/oconnd1/bin/runltspice', [], 'tmp', circ), integrality = True, disp = True, seed = 12345, updating = 'deferred', workers = 4)