def find_vde_index(self, id_wdescr, verbose=3): """Finds a voltage defined element MNA index. Parameters: id_wdescr (string): the element name, eg. 'V1'. Notice it includes both the id ('V') and the description ('1'). verbose (int): verbosity level, from 0 (silent) to 6 (debug). Returns: the index (int) """ vde_index = 0 found = False for elem in self: if is_elem_voltage_defined(elem): if elem.part_id.upper() == id_wdescr.upper(): found = True break else: vde_index += 1 if not found: printing.print_warning( "find_vde_index(): element %s was not found. This is a bug." % (id_wdescr, )) else: printing.print_info_line( ("%s found at index %d" % (id_wdescr, vde_index), 6), verbose) return vde_index
def run(circ, an_list=None): """ Processes an analysis vector: circ: the circuit instance an_queue: the list of analyses to be performed, if unset defaults to those queued with queue_analysis() Returns: the results (in dict form) """ results = {} if not an_list: an_list = _queue else: if type(an_list) == tuple: an_list = list(an_list) elif type(an_list) == dict: an_list = [an_list] # run(mycircuit, op1) elif type(an_list) == list: pass while len(an_list): an_item = an_list.pop(0) an_type = an_item.pop('type') if 'x0' in an_item and isinstance(an_item['x0'], str): printing.print_warning("%s has x0 set to %s, unavailable. Using 'None'." % (an_type.upper(), an_item['x0'])) an_item['x0'] = None r = analysis[an_type](circ, **an_item) results.update({an_type: r}) if an_type == 'op': _x0s.update({'op': r}) _x0s.update({'op+ic': icmodified_x0(circ, r)}) _handle_netlist_ics(circ, an_list, ic_list=[]) return results
def find_vde_index(self, id_wdescr, verbose=3): """Finds a voltage defined element MNA index. Parameters: id_wdescr (string): the element name, eg. 'V1'. Notice it includes both the id ('V') and the description ('1'). verbose (int): verbosity level, from 0 (silent) to 6 (debug). Returns: the index (int) """ vde_index = 0 found = False for elem in self: if is_elem_voltage_defined(elem): if elem.part_id.upper() == id_wdescr.upper(): found = True break else: vde_index += 1 if not found: printing.print_warning( "find_vde_index(): element %s was not found. This is a bug." % (id_wdescr,)) else: printing.print_info_line( ("%s found at index %d" % (id_wdescr, vde_index), 6), verbose) return vde_index
def process_postproc(postproc_list, title, results, outfilename, remote=False): """Runs the post-processing operations, such as plotting. postproc_list: list of post processing operations as returned by main() title: the deck title results: the results to be plotted (including the ones that are not needed) outfilename: if the plots are saved to disk, this is the filename without extension remote: boolean, do not show plots if True (such as ssh without X11 forwarding) Returns: None """ index = 0 if outfilename == 'stdout': printing.print_warning( "Plotting and printing the results to stdout are incompatible options. Plotting skipped." ) return for postproc in postproc_list: #print postproc["analysis"], results.keys(), results.has_key(postproc["analysis"]), results[postproc["analysis"]] is None #DEBUG plotting.plot_results( title, postproc["x"], postproc["l2l1"], results[postproc["analysis"]], "%s-%d.%s" % (outfilename, index, options.plotting_outtype)) index = index + 1 if len(postproc_list) and not remote: plotting.show_plots() return None
def check_step_and_points(step, points, period): """Sets consistently the step size and the number of points, according to the given period Returns: (points, step) """ if step is None and points is None: print "Warning: shooting had no step nor n. of points setted. Using", options.shooting_default_points, "points." points = options.shooting_default_points elif step is not None and points is not None: print "Warning: shooting had both step and n. of points setted. Using", step, "step. (NA)" points = None if points: step = (1.0 * period) / (points - 1) else: points = (1.0 * period) / step if points % 1 != 0: step = step + (step * (points % 1)) / int(points) points = int((1.0 * period) / step) printing.print_warning("adapted step is %g" % (step,)) else: points = int(points) points = points + \ 1 # 0 - N where xN is in reality the first point of the second period!! return (points, step)
def main(filename, outfile="stdout", tran_method=transient.TRAP.lower(), no_step_control=False, dc_guess='guess', print_circuit=False, remote=True, verbose=3): """This method allows calling ahkab from a Python script. """ printing.print_info_line( ("This is ahkab %s running with:" % (__version__), 6), verbose) printing.print_info_line( (" Python %s" % (sys.version.split('\n')[0], ), 6), verbose) printing.print_info_line((" Numpy %s" % (numpy.__version__), 6), verbose) printing.print_info_line((" Sympy %s" % (sympy.__version__), 6), verbose) printing.print_info_line((" Matplotlib %s" % (matplotlib.__version__), 6), verbose) utilities._set_execution_lock() read_netlist_from_stdin = (filename is None or filename == "-") (circ, directives, postproc_direct) = netlist_parser.parse_circuit(filename, read_netlist_from_stdin) check, reason = dc_analysis.check_circuit(circ) if not check: printing.print_general_error(reason) printing.print_circuit(circ) sys.exit(3) if verbose > 3 or print_circuit: print "Parsed circuit:" printing.print_circuit(circ) elif verbose > 1: print circ.title.upper() an_list = netlist_parser.parse_analysis(circ, directives) postproc_list = netlist_parser.parse_postproc(circ, an_list, postproc_direct) if len(an_list) > 0: printing.print_info_line(("Requested an.:", 4), verbose) if verbose >= 4: map(printing.print_analysis, an_list) else: if verbose: printing.print_warning("No analysis requested.") if len(an_list) > 0: results = process_analysis(an_list, circ, outfile, verbose, guess=dc_guess.lower()=="guess", \ cli_tran_method=tran_method, disable_step_control=no_step_control) else: printing.print_warning("Nothing to do. Quitting.") if len(an_list) > 0 and len(postproc_list) > 0 and len(results): process_postproc(postproc_list, circ.title, results, outfile, remote) utilities._unset_execution_lock() return results
def check_step_and_points(step, points, period): """Sets consistently the step size and the number of points, according to the given period Returns: (points, step) """ if step is None and points is None: print "Warning: shooting had no step nor n. of points setted. Using", options.shooting_default_points, "points." points = options.shooting_default_points elif step is not None and points is not None: print "Warning: shooting had both step and n. of points setted. Using", step, "step. (NA)" points = None if points: step = (1.0 * period) / (points - 1) else: points = (1.0 * period) / step if points % 1 != 0: step = step + (step * (points % 1)) / int(points) points = int((1.0 * period) / step) printing.print_warning("adapted step is %g" % (step, )) else: points = int(points) points = points + \ 1 # 0 - N where xN is in reality the first point of the second period!! return (points, step)
def run(circ, an_list=None): """ Processes an analysis vector: circ: the circuit instance an_queue: the list of analyses to be performed, if unset defaults to those queued with queue_analysis() Returns: the results (in dict form) """ results = {} if not an_list: an_list = _queue else: if type(an_list) == tuple: an_list = list(an_list) elif type(an_list) == dict: an_list = [an_list] # run(mycircuit, op1) elif type(an_list) == list: pass while len(an_list): an_item = an_list.pop(0) an_type = an_item.pop('type') if 'x0' in an_item and isinstance(an_item['x0'], str): printing.print_warning( "%s has x0 set to %s, unavailable. Using 'None'." % (an_type.upper(), an_item['x0'])) an_item['x0'] = None r = analysis[an_type](circ, **an_item) results.update({an_type: r}) if an_type == 'op': _x0s.update({'op': r}) _x0s.update({'op+ic': icmodified_x0(circ, r)}) _handle_netlist_ics(circ, an_list, ic_list=[]) return results
def add(self, atuple): if not len(atuple) == self._width: printing.print_warning("Attempted to add a element of wrong size to LIFO buffer. BUG?") return False else: self._the_real_buffer.insert(0, atuple) if len(self._the_real_buffer) > self._length: self._the_real_buffer = self._the_real_buffer[:self._length] return True
def add(self, atuple): if not len(atuple) == self._width: printing.print_warning( "Attempted to add a element of wrong size to LIFO buffer. BUG?" ) return False else: self._the_real_buffer.insert(0, atuple) if len(self._the_real_buffer) > self._length: self._the_real_buffer = self._the_real_buffer[:self._length] return True
def plot_results(title, y2y1_list, results, outfilename): """Plot the results. """ if results is None: printing.print_warning("No results available for plotting. Skipping.") return fig = pylab.figure() analysis = results.get_type().upper() gdata = [] x, xlabel = results.get_x(), results.get_xlabel() xunit = results.units[xlabel] yvu = [] for y2label, y1label in y2y1_list: if y1label is not None and y1label != '': data1 = results[y1label] line_label = y2label + "-" + y1label else: line_label = y2label data1 = 0 data2 = results[y2label] yvu += [(line_label, results.units[y2label])] gdata.append((data2 - data1, line_label)) if xlabel == 'w': xlog = True else: xlog = False setup_plot(fig, title, (xlabel, xunit), yvu, xlog=xlog) pylab.hold(True) ymax, ymin = None, None for y, label in gdata: [line] = pylab.plot(x, y, options.plotting_style, label=label + " (" + analysis + ")", mfc='w', lw=options.plotting_lw, mew=options.plotting_lw) line.set_mec(line.get_color()) # nice empty circles ymax = y.max() if ymax is None or y.max() > ymax else ymax ymin = y.min() if ymin is None or y.min() < ymin else ymin pylab.xlim((x.min(), x.max())) pylab.ylim((ymin - (ymax - ymin) * .01, ymax + (ymax - ymin) * .01)) pylab.hold(False) pylab.legend() if outfilename is not None and options.plotting_outtype is not None: save_figure(outfilename, fig) return
def plot_results(title, y2y1_list, results, outfilename): """Plot the results. """ if results is None: printing.print_warning("No results available for plotting. Skipping.") return fig = pylab.figure() analysis = results.get_type().upper() gdata = [] x, xlabel = results.get_x(), results.get_xlabel() xunit = results.units[xlabel] yvu = [] for y2label, y1label in y2y1_list: if y1label is not None and y1label != '': data1 = results[y1label] line_label = y2label + "-" + y1label else: line_label = y2label data1 = 0 data2 = results[y2label] yvu += [(line_label, results.units[y2label])] gdata.append((data2 - data1, line_label)) if xlabel == 'w': xlog = True else: xlog = False setup_plot(fig, title, (xlabel, xunit), yvu, xlog=xlog) pylab.hold(True) ymax, ymin = None, None for y, label in gdata: [line] = pylab.plot( x, y, options.plotting_style, label=label + " (" + analysis + ")", mfc='w', lw=options.plotting_lw, mew=options.plotting_lw) line.set_mec(line.get_color()) # nice empty circles ymax = y.max() if ymax is None or y.max() > ymax else ymax ymin = y.min() if ymin is None or y.min() < ymin else ymin pylab.xlim((x.min(), x.max())) pylab.ylim((ymin - (ymax - ymin) * .01, ymax + (ymax - ymin) * .01)) pylab.hold(False) pylab.legend() if outfilename is not None and options.plotting_outtype is not None: save_figure(outfilename, fig) return
def main(filename, outfile="stdout", tran_method=transient.TRAP.lower(), no_step_control=False, dc_guess='guess', print_circuit=False, remote=True, verbose=3): """This method allows calling ahkab from a Python script. """ printing.print_info_line(("This is ahkab %s running with:" %(__version__),6), verbose) printing.print_info_line((" Python %s" % (sys.version.split('\n')[0],),6), verbose) printing.print_info_line((" Numpy %s" % (numpy.__version__),6), verbose) printing.print_info_line((" Sympy %s" % (sympy.__version__),6), verbose) printing.print_info_line((" Matplotlib %s" % (matplotlib.__version__),6), verbose) utilities._set_execution_lock() read_netlist_from_stdin = (filename is None or filename == "-") (circ, directives, postproc_direct) = netlist_parser.parse_circuit(filename, read_netlist_from_stdin) check, reason = dc_analysis.check_circuit(circ) if not check: printing.print_general_error(reason) printing.print_circuit(circ) sys.exit(3) if verbose > 3 or print_circuit: print "Parsed circuit:" printing.print_circuit(circ) elif verbose > 1: print circ.title.upper() an_list = netlist_parser.parse_analysis(circ, directives) postproc_list = netlist_parser.parse_postproc(circ, an_list, postproc_direct) if len(an_list) > 0: printing.print_info_line(("Requested an.:", 4), verbose) if verbose >= 4: map(printing.print_analysis, an_list) else: if verbose: printing.print_warning("No analysis requested.") if len(an_list) > 0: results = process_analysis(an_list, circ, outfile, verbose, guess=dc_guess.lower()=="guess", \ cli_tran_method=tran_method, disable_step_control=no_step_control) else: printing.print_warning("Nothing to do. Quitting.") if len(an_list) > 0 and len(postproc_list) > 0 and len(results): process_postproc(postproc_list, circ.title, results, outfile, remote) utilities._unset_execution_lock() return results
def plot_results(title, xvarname, y2y1_list, results, outfilename): """Plot the results. """ if results is None: printing.print_warning("No results available for plotting. Skipping.") return fig = pylab.figure() analysis = results.get_type().upper() xunit = results.units[xvarname] gdata = [] x = results[xvarname] yvu = [] for y2label, y1label in y2y1_list: if y1label is not None and y1label != '': data1 = results[y1label] line_label = y2label + "-" + y1label else: line_label = y2label data1 = 0 data2 = results[y2label] yvu += [(line_label, results.units[y2label])] gdata.append((data2 - data1, line_label)) if xvarname == 'w': xlog = True else: xlog = False setup_plot(fig, title, (xvarname, xunit), yvu, xlog=xlog) pylab.hold(True) for y, label in gdata: # pylab wants matrices in the form: N,1, while results come as (1, N) -> Transpose pylab.plot( x.T, y.T, options.plotting_style, label=label + " (" + analysis + ")", ) pylab.xlim((x.min(), x.max())) pylab.hold(False) pylab.legend() if outfilename is not None and options.plotting_outtype is not None: save_figure(outfilename, fig) return
def check_ground_paths(mna, circ, reduced_mna=True, verbose=3): """Checks that every node has a DC path to ground, wheather through nonlinear or linear elements. - This does not ensure that the circuit will have a DC solution. - A node without DC path to ground would be rescued (likely) by GMIN so (for the time being at least) we do *not* halt the execution. - Also, two series capacitors always fail this check (GMIN saves us) Bottom line: if there is no DC path to ground, there is probably a mistake in the netlist. Print a warning. """ test_passed = True if reduced_mna: # reduced_correction r_c = 1 else: r_c = 0 to_be_checked_for_nonlinear_paths = [] for node in circ.nodes_dict.iterkeys(): if node == 0: continue # ground if mna[node - r_c, node - r_c] == 0 and not mna[node - r_c, len(circ.nodes_dict) - r_c:].any(): to_be_checked_for_nonlinear_paths.append(node) for node in to_be_checked_for_nonlinear_paths: node_is_nl_op = False for elem in circ: if not elem.is_nonlinear: continue ops = elem.get_output_ports() for op in ops: if op.count(node): node_is_nl_op = True if not node_is_nl_op: if verbose: printing.print_warning("No path to ground from node " + circ.nodes_dict[node]) test_passed = False return test_passed
def plot_results(title, xvarname, y2y1_list, results, outfilename): """Plot the results. """ if results is None: printing.print_warning("No results available for plotting. Skipping.") return fig = pylab.figure() analysis = results.get_type().upper() xunit = results.units[xvarname] gdata = [] x = results[xvarname] yvu = [] for y2label, y1label in y2y1_list: if y1label is not None and y1label != "": data1 = results[y1label] line_label = y2label + "-" + y1label else: line_label = y2label data1 = 0 data2 = results[y2label] yvu += [(line_label, results.units[y2label])] gdata.append((data2 - data1, line_label)) if xvarname == "w": xlog = True else: xlog = False setup_plot(fig, title, (xvarname, xunit), yvu, xlog=xlog) pylab.hold(True) for y, label in gdata: # pylab wants matrices in the form: N,1, while results come as (1, N) -> Transpose pylab.plot(x.T, y.T, options.plotting_style, label=label + " (" + analysis + ")") pylab.xlim((x.min(), x.max())) pylab.hold(False) pylab.legend() if outfilename is not None and options.plotting_outtype is not None: save_figure(outfilename, fig) return
def process_postproc(postproc_list, title, results, outfilename): """Runs the post-processing operations, such as plotting. postproc_list: list of post processing operations as returned by main() title: the deck title results: the results to be plotted (including the ones that are not needed) outfilename: if the plots are saved to disk, this is the filename without extension Returns: None """ index = 0 if outfilename == 'stdout': printing.print_warning( "Plotting and printing the results to stdout are incompatible options. Plotting skipped.") return for postproc in postproc_list: plotting.plot_results(title, postproc["l2l1"], results[ postproc["analysis"]], "%s-%d.%s" % (outfilename, index, options.plotting_outtype)) index = index + 1 if len(postproc_list) and options.plotting_show_plots: plotting.show_plots() return None
def process_postproc(postproc_list, title, results, outfilename, remote=False): """Runs the post-processing operations, such as plotting. postproc_list: list of post processing operations as returned by main() title: the deck title results: the results to be plotted (including the ones that are not needed) outfilename: if the plots are saved to disk, this is the filename without extension remote: boolean, do not show plots if True (such as ssh without X11 forwarding) Returns: None """ index = 0 if outfilename == 'stdout': printing.print_warning("Plotting and printing the results to stdout are incompatible options. Plotting skipped.") return for postproc in postproc_list: #print postproc["analysis"], results.keys(), results.has_key(postproc["analysis"]), results[postproc["analysis"]] is None #DEBUG plotting.plot_results(title, postproc["x"], postproc["l2l1"], results[postproc["analysis"]], "%s-%d.%s" % (outfilename, index, options.plotting_outtype)) index = index +1 if len(postproc_list) and not remote: plotting.show_plots() return None
def process_postproc(postproc_list, title, results, outfilename): """Runs the post-processing operations, such as plotting. postproc_list: list of post processing operations as returned by main() title: the deck title results: the results to be plotted (including the ones that are not needed) outfilename: if the plots are saved to disk, this is the filename without extension Returns: None """ index = 0 if outfilename == 'stdout': printing.print_warning( "Plotting and printing the results to stdout are incompatible options. Plotting skipped." ) return for postproc in postproc_list: plotting.plot_results( title, postproc["l2l1"], results[postproc["analysis"]], "%s-%d.%s" % (outfilename, index, options.plotting_outtype)) index = index + 1 if len(postproc_list) and options.plotting_show_plots: plotting.show_plots() return None
def check_ground_paths(mna, circ, reduced_mna=True): """Checks that every node has a DC path to ground, wheather through nonlinear or linear elements. - This does not ensure that the circuit will have a DC solution. - A node without DC path to ground would be rescued (likely) by GMIN so (for the time being at least) we do *not* halt the execution. - Also, two series capacitors always fail this check (GMIN saves us) Bottom line: if there is no DC path to ground, there is probably a mistake in the netlist. Print a warning. """ test_passed = True if reduced_mna: # reduced_correction r_c = 1 else: r_c = 0 to_be_checked_for_nonlinear_paths = [] for node in circ.nodes_dict.iterkeys(): if node == 0: continue # ground if mna[node - r_c, node - r_c] == 0 and not mna[node - r_c, len(circ.nodes_dict) - r_c :].any(): to_be_checked_for_nonlinear_paths.append(node) for node in to_be_checked_for_nonlinear_paths: node_is_nl_op = False for elem in circ.elements: if not elem.is_nonlinear: continue ops = elem.get_output_ports() for op in ops: if op.count(node): node_is_nl_op = True if not node_is_nl_op: printing.print_warning("No path to ground from node " + circ.nodes_dict[node]) test_passed = False return test_passed
def generate_mna_and_N(circ, opts, ac=False): """Generates a symbolic Modified Nodal Analysis matrix and N vector. """ #print options n_of_nodes = len(circ.nodes_dict) mna = smzeros(n_of_nodes) N = smzeros((n_of_nodes, 1)) s = sympy.Symbol("s", complex=True) subs_g = {} #process_elements() for elem in circ.elements: #if elem.is_nonlinear and not (isinstance(elem, mosq.mosq_device) or isinstance(elem, ekv.ekv_device)): # print "Skipped elem "+elem.letter_id.upper()+elem.descr + ": not implemented." # continue if isinstance(elem, devices.resistor): # we use conductances instead of 1/R because there is a significant # overhead handling many 1/R terms in sympy. if elem.is_symbolic: R = sympy.Symbol(elem.letter_id.upper()+elem.descr, real=True) G = sympy.Symbol("G"+elem.descr, real=True) # but we keep track of which is which and substitute back after solving. subs_g.update({G:1/R}) else: R = elem.R G = 1.0/R #mna[elem.n1, elem.n1] = mna[elem.n1, elem.n1] + 1/R #mna[elem.n1, elem.n2] = mna[elem.n1, elem.n2] - 1/R #mna[elem.n2, elem.n1] = mna[elem.n2, elem.n1] - 1/R #mna[elem.n2, elem.n2] = mna[elem.n2, elem.n2] + 1/R mna[elem.n1, elem.n1] = mna[elem.n1, elem.n1] + G mna[elem.n1, elem.n2] = mna[elem.n1, elem.n2] - G mna[elem.n2, elem.n1] = mna[elem.n2, elem.n1] - G mna[elem.n2, elem.n2] = mna[elem.n2, elem.n2] + G elif isinstance(elem, devices.capacitor): if ac: if elem.is_symbolic: capa = sympy.Symbol(elem.letter_id.upper()+elem.descr, real=True) else: capa = elem.C mna[elem.n1, elem.n1] = mna[elem.n1, elem.n1] + s*capa mna[elem.n1, elem.n2] = mna[elem.n1, elem.n2] - s*capa mna[elem.n2, elem.n2] = mna[elem.n2, elem.n2] + s*capa mna[elem.n2, elem.n1] = mna[elem.n2, elem.n1] - s*capa else: pass elif isinstance(elem, devices.inductor): pass elif isinstance(elem, devices.gisource): if elem.is_symbolic: alpha = sympy.Symbol(elem.letter_id+elem.descr, real=True) else: alpha = elem.alpha mna[elem.n1, elem.sn1] = mna[elem.n1, elem.sn1] + alpha mna[elem.n1, elem.sn2] = mna[elem.n1, elem.sn2] - alpha mna[elem.n2, elem.sn1] = mna[elem.n2, elem.sn1] - alpha mna[elem.n2, elem.sn2] = mna[elem.n2, elem.sn2] + alpha elif isinstance(elem, devices.isource): if elem.is_symbolic: IDC = sympy.Symbol(elem.letter_id.upper()+elem.descr, real=True) else: IDC = elem.idc N[elem.n1, 0] = N[elem.n1, 0] + IDC N[elem.n2, 0] = N[elem.n2, 0] - IDC elif isinstance(elem, mosq.mosq_device) or isinstance(elem, ekv.ekv_device): gm = sympy.Symbol('gm_'+elem.letter_id+elem.descr, real=True) mna[elem.n1, elem.ng] = mna[elem.n1, elem.ng] + gm mna[elem.n1, elem.n2] = mna[elem.n1, elem.n2] - gm mna[elem.n2, elem.ng] = mna[elem.n2, elem.ng] - gm mna[elem.n2, elem.n2] = mna[elem.n2, elem.n2] + gm if opts['r0s']: r0 = sympy.Symbol('r0_'+elem.letter_id+elem.descr, real=True) mna[elem.n1, elem.n1] = mna[elem.n1, elem.n1] + 1/r0 mna[elem.n1, elem.n2] = mna[elem.n1, elem.n2] - 1/r0 mna[elem.n2, elem.n1] = mna[elem.n2, elem.n1] - 1/r0 mna[elem.n2, elem.n2] = mna[elem.n2, elem.n2] + 1/r0 elif isinstance(elem, diode.diode): gd = sympy.Symbol("g"+elem.letter_id+elem.descr) mna[elem.n1, elem.n1] = mna[elem.n1, elem.n1] + gd mna[elem.n1, elem.n2] = mna[elem.n1, elem.n2] - gd mna[elem.n2, elem.n1] = mna[elem.n2, elem.n1] - gd mna[elem.n2, elem.n2] = mna[elem.n2, elem.n2] + gd elif isinstance(elem, devices.inductor_coupling): pass # this is taken care of within the inductors elif circuit.is_elem_voltage_defined(elem): pass #we'll add its lines afterwards else: printing.print_warning("Skipped elem %s: not implemented." % (elem.letter_id.upper()+elem.descr,)) pre_vde = mna.shape[0] for elem in circ.elements: if circuit.is_elem_voltage_defined(elem): index = mna.shape[0] #get_matrix_size(mna)[0] mna = expand_matrix(mna, add_a_row=True, add_a_col=True) N = expand_matrix(N, add_a_row=True, add_a_col=False) # KCL mna[elem.n1, index] = +1 mna[elem.n2, index] = -1 # KVL mna[index, elem.n1] = +1 mna[index, elem.n2] = -1 if isinstance(elem, devices.vsource): if elem.is_symbolic: VDC = sympy.Symbol(elem.letter_id.upper() + elem.descr, real=True) else: VDC = elem.vdc N[index, 0] = -VDC elif isinstance(elem, devices.evsource): if elem.is_symbolic: alpha = sympy.Symbol(elem.letter_id.upper() + elem.descr, real=True) else: alpha = elem.alpha mna[index, elem.sn1] = -alpha mna[index, elem.sn2] = +alpha elif isinstance(elem, devices.inductor): if ac: if elem.is_symbolic: L = sympy.Symbol(elem.letter_id.upper() + elem.descr, real=True) else: L = elem.L mna[index, index] = -s*L else: pass # already so: commented out # N[index,0] = 0 elif isinstance(elem, devices.hvsource): printing.print_warning("symbolic.py: BUG - hvsources are not implemented yet.") sys.exit(33) for elem in circ.elements: if circuit.is_elem_voltage_defined(elem): if isinstance(elem, devices.inductor): if ac: # find its index to know which column corresponds to its current this_index = circ.find_vde_index("L"+elem.descr, verbose=0) for cd in elem.coupling_devices: if cd.is_symbolic: M = sympy.Symbol("M" + cd.descr, real=True) else: M = cd.K # get id+descr of the other inductor (eg. "L32") other_id_wdescr = cd.get_other_inductor("L"+elem.descr) # find its index to know which column corresponds to its current other_index = circ.find_vde_index(other_id_wdescr, verbose=0) # add the term. #print "other_index: "+str(other_index) #print "this_index: "+str(this_index) mna[pre_vde+this_index,pre_vde+other_index] += -s*M #print mna else: pass # already so: commented out # N[index,0] = 0 #all done return (mna, N, subs_g)
def mdn_solver(x, mna, circ, T, MAXIT, nv, locked_nodes, time=None, print_steps=False, vector_norm=lambda v: max(abs(v)), debug=True): """ Solves a problem like F(x) = 0 using the Newton Algorithm with a variable damping td. Where: F(x) = mna*x + T + T(x) mna is the Modified Network Analysis matrix of the circuit T(x) is the contribute of nonlinear elements to KCL T -> independent sources, time invariant and invariant x is the initial guess. Every x is given by: x = x + td*dx Where td is a damping coefficient to avoid overflow in non-linear components and excessive oscillation in the very first iteration. Afterwards td=1 To calculate td, an array of locked nodes is needed. The convergence check is done this way: if (vector_norm(dx) < alpha*vector_norm(x) + beta) and (vector_norm(residuo) < alpha*vector_norm(x) + beta): beta should be the machine precision, alpha 1e-(N+1) where N is the number of the significative digits you wish to have. Parameters: x: the initial guess. If set to None, it will be initialized to all zeros. Specifying a initial guess may improve the convergence time of the algorithm and determine which solution (if any) is found if there are more than one. mna: the Modified Network Analysis matrix of the circuit, reduced, see above element_list: T: see above. MAXIT: Maximum iterations that the method may perform. nv: number of nodes in the circuit (counting the ref, 0) locked_nodes: see get_td() and dc_solve(), generated by circ.get_locked_nodes() time: the value of time to be passed to non_linear _and_ time variant elements. print_steps: show a progress indicator vector_norm: Returns a tuple with: the solution, the remaining error, a boolean that is true whenever the method exits because of a successful convergence check the number of NR iterations performed """ # OLD COMMENT: FIXME REWRITE: solve through newton # problem is F(x)= mna*x +H(x) = 0 # H(x) = N + T(x) # lets say: J = dF/dx = mna + dT(x)/dx # J*dx = -1*(mna*x+N+T(x)) # dT/dx � lo jacobiano -> g_eq (o gm) # print_steps = False # locked_nodes = get_locked_nodes(element_list) mna_size = mna.shape[0] nonlinear_circuit = circ.is_nonlinear() tick = ticker.ticker(increments_for_step=1) tick.display(print_steps) if x is None: x = numpy.mat(numpy.zeros((mna_size, 1))) # if no guess was specified, its all zeros else: if not x.shape[0] == mna_size: raise Exception, "x0s size is different from expected: " + \ str(x.shape[0]) + " " + str(mna_size) if T is None: printing.print_warning( "dc_analysis.mdn_solver called with T==None, setting T=0. BUG or no sources in circuit?" ) T = numpy.mat(numpy.zeros((mna_size, 1))) converged = False iteration = 0L while iteration < MAXIT: # newton iteration counter iteration += 1 tick.step(print_steps) if nonlinear_circuit: # build dT(x)/dx (stored in J) and Tx(x) J, Tx = build_J_and_Tx(x, mna_size, circ, time) J = J + mna else: J = mna Tx = 0 residuo = mna * x + T + Tx dx = numpy.linalg.inv(J) * (-1 * residuo) x = x + get_td(dx, locked_nodes, n=iteration) * dx if not nonlinear_circuit: converged = True break elif convergence_check(x, dx, residuo, nv - 1)[0]: converged = True break # if vector_norm(dx) == numpy.nan: #Overflow # raise OverflowError tick.hide(print_steps) if debug and not converged: # re-run the convergence check, only this time get the results # by node, so we can show to the users which nodes are misbehaving. converged, convergence_by_node = convergence_check(x, dx, residuo, nv - 1, debug=True) else: convergence_by_node = [] return (x, residuo, converged, iteration, convergence_by_node)
def process_analysis(an_list, circ, outfile, verbose, cli_tran_method=None, guess=True, disable_step_control=False): """ Processes an analysis vector: an_list: the list of analysis to be performed, as returned by netlist_parser circ: the circuit instance, returned by netlist_parser outfile: a filename. Results will be written to it. If set to stdout, prints to stdout verbose: verbosity level cli_tran_method: force the specified method in each tran analysis (see transient.py) guess: use the builtin method get_dc_guess to guess x0 Returns: None """ x0_op = None x0_ic_dict = {} results = {} for directive in [x for x in an_list if x["type"] == "ic"]: x0_ic_dict.update({ directive["name"]:\ dc_analysis.build_x0_from_user_supplied_ic(circ, voltages_dict=directive["vdict"], currents_dict=directive["cdict"]) }) for an in an_list: if outfile != 'stdout': data_filename = outfile + "." + an["type"] else: data_filename = outfile if an["type"] == "ic": continue if an["type"] == "op": if not an.has_key('guess_label') or an["guess_label"] is None: x0_op = dc_analysis.op_analysis(circ, guess=guess, data_filename=data_filename, verbose=verbose) else: if not an["guess_label"] in x0_ic_dict: printing.print_warning( "op: guess is set but no matching .ic directive was found." ) printing.print_warning( "op: using built-in guess method: " + str(guess)) x0_op = dc_analysis.op_analysis(circ, guess=guess, verbose=verbose) else: x0_op = dc_analysis.op_analysis( circ, guess=False, x0=x0_ic_dict[an["guess_label"]], verbose=verbose) sol = x0_op elif an["type"] == "dc": if an["source_name"][0].lower() == "v": elem_type = "vsource" elif an["source_name"][0].lower() == "i": elem_type = "isource" else: printing.print_general_error( "Type of sweep source is unknown: " + an[1][0]) sys.exit(1) sol = dc_analysis.dc_analysis( circ, start=an["start"], stop=an["stop"], step=an["step"], \ type_descr=(elem_type, an["source_name"][1:]), xguess=x0_op, data_filename=data_filename, guess=guess, stype=an['stype'], verbose=verbose) #{"type":"tran", "tstart":tstart, "tstop":tstop, "tstep":tstep, "uic":uic, "method":method, "ic_label":ic_label} elif an["type"] == "tran": if cli_tran_method is not None: tran_method = cli_tran_method.upper() elif an["method"] is not None: tran_method = an["method"].upper() else: tran_method = options.default_tran_method # setup the initial condition (t=0) according to uic # uic = 0 -> all node voltages and currents are zero # uic = 1 -> node voltages and currents are those computed in the last OP analysis # uic = 2 -> node voltages and currents are those computed in the last OP analysis # combined with the ic=XX directive found in capacitors and inductors # uic = 3 -> use a .ic directive defined by the user uic = an["uic"] if uic == 0: x0 = None elif uic == 1: if x0_op is None: printing.print_general_error( "uic is set to 1, but no op has been calculated yet.") sys.exit(51) x0 = x0_op elif uic == 2: if x0_op is None: printing.print_general_error( "uic is set to 2, but no op has been calculated yet.") sys.exit(51) x0 = dc_analysis.modify_x0_for_ic(circ, x0_op) elif uic == 3: if an["ic_label"] is None: printing.print_general_error( "uic is set to 3, but param ic=<ic_label> was not defined." ) sys.exit(53) elif not an["ic_label"] in x0_ic_dict: printing.print_general_error("uic is set to 3, but no .ic directive named %s was found." \ %(str(an["ic_label"]),)) sys.exit(54) x0 = x0_ic_dict[an["ic_label"]] sol = transient.transient_analysis(circ, \ tstart=an["tstart"], tstep=an["tstep"], tstop=an["tstop"], \ x0=x0, mna=None, N=None, verbose=verbose, data_filename=data_filename, \ use_step_control=(not disable_step_control), method=tran_method) elif an["type"] == "shooting": if an["method"] == "brute-force": sol = bfpss.bfpss(circ, period=an["period"], step=an["step"], mna=None, Tf=None, \ D=None, points=an["points"], autonomous=an["autonomous"], x0=x0_op, \ data_filename=data_filename, verbose=verbose) elif an["method"] == "shooting": sol = shooting.shooting(circ, period=an["period"], step=an["step"], mna=None, \ Tf=None, D=None, points=an["points"], autonomous=an["autonomous"], \ data_filename=data_filename, verbose=verbose) elif an["type"] == "symbolic": if not 'subs' in an.keys(): an.update({'subs': None}) sol = symbolic.solve(circ, an['source'], opts={'ac': an['ac']}, subs=an['subs'], verbose=verbose) elif an["type"] == "ac": sol = ac.ac_analysis(circ=circ, start=an['start'], nsteps=an['nsteps'], \ stop=an['stop'], step_type='LOG', xop=x0_op, mna=None,\ data_filename=data_filename, verbose=verbose) elif an["type"] == "temp": constants.T = utilities.Celsius2Kelvin(an['temp']) results.update({an["type"]: sol}) return results
def solve(circ, tf_source=None, subs=None, opts=None, verbose=3): """Attempt a symbolic solution of the circuit. circ: the circuit instance to be simulated. tf_source: the name (string) of the source to be used as input for the transfer function. If None, no transfer function is evaluated. subs: a dictionary of sympy Symbols to be substituted. It makes solving the circuit easier. Eg. {R1:R2} - replace R1 with R2. It can be generated with parse_substitutions() opts: dict of 'option':boolean to be taken into account in simulation. currently 'r0s' and 'ac' are the only options considered. verbose: verbosity level 0 (silent) to 6 (painful). Returns: a dictionary with the solutions. """ if opts is None: # load the defaults opts = {'r0s':True, 'ac':False} if not 'r0s' in opts.keys(): opts.update({'r0s':True}) if not 'ac' in opts.keys(): opts.update({'ac':False}) if subs is None: subs = {} # no subs by default if not opts['ac']: printing.print_info_line(("Starting symbolic DC...", 1), verbose) else: printing.print_info_line(("Starting symbolic AC...", 1), verbose) printing.print_info_line(("Building symbolic MNA, N and x...", 2), verbose, print_nl=False) mna, N, subs_g = generate_mna_and_N(circ, opts, opts['ac']) x = get_variables(circ) mna = mna[1:, 1:] N = N[1:, :] printing.print_info_line((" done.", 2), verbose) printing.print_info_line(("Performing variable substitutions...", 5), verbose) mna, N = apply_substitutions(mna, N, subs) printing.print_info_line(("MNA matrix (reduced):", 5), verbose) if verbose > 5: print sympy.sstr(mna) printing.print_info_line(("N matrix (reduced):", 5), verbose) if verbose > 5: print sympy.sstr(N) printing.print_info_line(("Building equations...", 2), verbose) eq = [] for i in to_real_list(mna * x + N): eq.append(sympy.Eq(i, 0)) x = to_real_list(x) if verbose > 4: printing.print_symbolic_equations(eq) print "To be solved for:" print x #print "Matrix is singular: ", (mna.det() == 0) #print -1.0*mna.inv()*N #too heavy #print sympy.solve_linear_system(mna.row_join(-N), x) printing.print_info_line(("Performing auxiliary simplification...", 2), verbose) eq, x, sol_h = help_the_solver(eq, x) if len(eq): if verbose > 3: print "Symplified sytem:" printing.print_symbolic_equations(eq) print "To be solved for:" print x printing.print_info_line(("Solving...", 2), verbose) if options.symb_internal_solver: sol = local_solve(eq, x) else: sol = sympy.solve(eq, x, manual=options.symb_sympy_manual_solver, simplify=True) if sol is not None: sol.update(sol_h) else: sol = sol_h else: printing.print_info_line(("Auxiliary simplification solved the problem.", 3), verbose) sol = sol_h for ks in sol.keys(): sol.update({ks:sol[ks].subs(subs_g)}) #sol = sol_to_dict(sol, x) if sol == {}: printing.print_warning("No solutions. Check the netlist.") else: printing.print_info_line(("Success!", 2), verbose) if verbose > 1: print "Results:" printing.print_symbolic_results(sol) if tf_source is not None: src = sympy.Symbol(tf_source.upper(), real=True) printing.print_info_line(("Calculating small-signal symbolic transfer functions (%s))..."%(str(src),), 2), verbose, print_nl=False) tfs = calculate_gains(sol, src) printing.print_info_line(("done.", 2), verbose) printing.print_info_line(("Small-signal symbolic transfer functions:", 1), verbose) printing.print_symbolic_transfer_functions(tfs) else: tfs = None return sol, tfs
def set_temperature(T): T = float(T) if T > 300: printing.print_warning(u"The temperature will be set to %f \xB0 C.") constants.T = utilities.Celsius2Kelvin(T)
def process_analysis(an_list, circ, outfile, verbose, cli_tran_method=None, guess=True, disable_step_control=False): """ Processes an analysis vector: an_list: the list of analysis to be performed, as returned by netlist_parser circ: the circuit instance, returned by netlist_parser outfile: a filename. Results will be written to it. If set to stdout, prints to stdout verbose: verbosity level cli_tran_method: force the specified method in each tran analysis (see transient.py) guess: use the builtin method get_dc_guess to guess x0 Returns: None """ x0_op = None x0_ic_dict = {} results = {} for directive in [ x for x in an_list if x["type"] == "ic" ]: x0_ic_dict.update({ directive["name"]:\ dc_analysis.build_x0_from_user_supplied_ic(circ, voltages_dict=directive["vdict"], currents_dict=directive["cdict"]) }) for an in an_list: if outfile != 'stdout': data_filename = outfile + "." + an["type"] else: data_filename = outfile if an["type"] == "ic": continue if an["type"] == "op": if not an.has_key('guess_label') or an["guess_label"] is None: x0_op = dc_analysis.op_analysis(circ, guess=guess, data_filename=data_filename, verbose=verbose) else: if not an["guess_label"] in x0_ic_dict: printing.print_warning("op: guess is set but no matching .ic directive was found.") printing.print_warning("op: using built-in guess method: "+str(guess)) x0_op = dc_analysis.op_analysis(circ, guess=guess, verbose=verbose) else: x0_op = dc_analysis.op_analysis(circ, guess=False, x0=x0_ic_dict[an["guess_label"]], verbose=verbose) sol = x0_op elif an["type"] == "dc": if an["source_name"][0].lower() == "v": elem_type = "vsource" elif an["source_name"][0].lower() == "i": elem_type = "isource" else: printing.print_general_error("Type of sweep source is unknown: " + an[1][0]) sys.exit(1) sol = dc_analysis.dc_analysis( circ, start=an["start"], stop=an["stop"], step=an["step"], \ type_descr=(elem_type, an["source_name"][1:]), xguess=x0_op, data_filename=data_filename, guess=guess, stype=an['stype'], verbose=verbose) #{"type":"tran", "tstart":tstart, "tstop":tstop, "tstep":tstep, "uic":uic, "method":method, "ic_label":ic_label} elif an["type"] == "tran": if cli_tran_method is not None: tran_method = cli_tran_method.upper() elif an["method"] is not None: tran_method = an["method"].upper() else: tran_method = options.default_tran_method # setup the initial condition (t=0) according to uic # uic = 0 -> all node voltages and currents are zero # uic = 1 -> node voltages and currents are those computed in the last OP analysis # uic = 2 -> node voltages and currents are those computed in the last OP analysis # combined with the ic=XX directive found in capacitors and inductors # uic = 3 -> use a .ic directive defined by the user uic = an["uic"] if uic == 0: x0 = None elif uic == 1: if x0_op is None: printing.print_general_error("uic is set to 1, but no op has been calculated yet.") sys.exit(51) x0 = x0_op elif uic == 2: if x0_op is None: printing.print_general_error("uic is set to 2, but no op has been calculated yet.") sys.exit(51) x0 = dc_analysis.modify_x0_for_ic(circ, x0_op) elif uic == 3: if an["ic_label"] is None: printing.print_general_error("uic is set to 3, but param ic=<ic_label> was not defined.") sys.exit(53) elif not an["ic_label"] in x0_ic_dict: printing.print_general_error("uic is set to 3, but no .ic directive named %s was found." \ %(str(an["ic_label"]),)) sys.exit(54) x0 = x0_ic_dict[an["ic_label"]] sol = transient.transient_analysis(circ, \ tstart=an["tstart"], tstep=an["tstep"], tstop=an["tstop"], \ x0=x0, mna=None, N=None, verbose=verbose, data_filename=data_filename, \ use_step_control=(not disable_step_control), method=tran_method) elif an["type"] == "shooting": if an["method"]=="brute-force": sol = bfpss.bfpss(circ, period=an["period"], step=an["step"], mna=None, Tf=None, \ D=None, points=an["points"], autonomous=an["autonomous"], x0=x0_op, \ data_filename=data_filename, verbose=verbose) elif an["method"]=="shooting": sol = shooting.shooting(circ, period=an["period"], step=an["step"], mna=None, \ Tf=None, D=None, points=an["points"], autonomous=an["autonomous"], \ data_filename=data_filename, verbose=verbose) elif an["type"] == "symbolic": if not 'subs' in an.keys(): an.update({'subs':None}) sol = symbolic.solve(circ, an['source'], opts={'ac':an['ac']}, subs=an['subs'], verbose=verbose) elif an["type"] == "ac": sol = ac.ac_analysis(circ=circ, start=an['start'], nsteps=an['nsteps'], \ stop=an['stop'], step_type='LOG', xop=x0_op, mna=None,\ data_filename=data_filename, verbose=verbose) elif an["type"] == "temp": constants.T = utilities.Celsius2Kelvin(an['temp']) results.update({an["type"]:sol}) return results
def generate_mna_and_N(circ, opts, ac=False, verbose=3): """Generates a symbolic Modified Nodal Analysis matrix and N vector. """ # print options n_of_nodes = len(circ.nodes_dict) mna = smzeros(n_of_nodes) N = smzeros((n_of_nodes, 1)) s = sympy.Symbol('s', complex=True) subs_g = {} for elem in circ: if isinstance(elem, devices.Resistor): # we use conductances instead of 1/R because there is a significant # overhead handling many 1/R terms in sympy. if elem.is_symbolic: R = sympy.Symbol(elem.part_id.upper(), real=True, positive=True) G = sympy.Symbol('G' + elem.part_id[1:], real=True, positive=True) # but we keep track of which is which and substitute back after # solving. subs_g.update({G: 1 / R}) else: R = elem.value G = 1.0 / R mna[elem.n1, elem.n1] = mna[elem.n1, elem.n1] + G mna[elem.n1, elem.n2] = mna[elem.n1, elem.n2] - G mna[elem.n2, elem.n1] = mna[elem.n2, elem.n1] - G mna[elem.n2, elem.n2] = mna[elem.n2, elem.n2] + G elif isinstance(elem, devices.Capacitor): if ac: if elem.is_symbolic: capa = sympy.Symbol(elem.part_id.upper(), real=True, positive=True) else: capa = elem.value mna[elem.n1, elem.n1] = mna[elem.n1, elem.n1] + s * capa mna[elem.n1, elem.n2] = mna[elem.n1, elem.n2] - s * capa mna[elem.n2, elem.n2] = mna[elem.n2, elem.n2] + s * capa mna[elem.n2, elem.n1] = mna[elem.n2, elem.n1] - s * capa else: pass elif isinstance(elem, devices.Inductor): pass elif isinstance(elem, devices.GISource): if elem.is_symbolic: alpha = sympy.Symbol(elem.part_id.upper(), real=True) else: alpha = elem.value mna[elem.n1, elem.sn1] = mna[elem.n1, elem.sn1] + alpha mna[elem.n1, elem.sn2] = mna[elem.n1, elem.sn2] - alpha mna[elem.n2, elem.sn1] = mna[elem.n2, elem.sn1] - alpha mna[elem.n2, elem.sn2] = mna[elem.n2, elem.sn2] + alpha elif isinstance(elem, devices.ISource): if elem.is_symbolic: IDC = sympy.Symbol(elem.part_id.upper(), real=True) else: IDC = elem.dc_value N[elem.n1, 0] = N[elem.n1, 0] + IDC N[elem.n2, 0] = N[elem.n2, 0] - IDC elif isinstance(elem, mosq.mosq_device) or isinstance( elem, ekv.ekv_device): gm = sympy.Symbol('gm_' + elem.part_id, real=True, positive=True) mna[elem.n1, elem.ng] = mna[elem.n1, elem.ng] + gm mna[elem.n1, elem.n2] = mna[elem.n1, elem.n2] - gm mna[elem.n2, elem.ng] = mna[elem.n2, elem.ng] - gm mna[elem.n2, elem.n2] = mna[elem.n2, elem.n2] + gm if opts['r0s']: r0 = sympy.Symbol('r0_' + elem.part_id, real=True, positive=True) mna[elem.n1, elem.n1] = mna[elem.n1, elem.n1] + 1 / r0 mna[elem.n1, elem.n2] = mna[elem.n1, elem.n2] - 1 / r0 mna[elem.n2, elem.n1] = mna[elem.n2, elem.n1] - 1 / r0 mna[elem.n2, elem.n2] = mna[elem.n2, elem.n2] + 1 / r0 elif isinstance(elem, diode.diode): gd = sympy.Symbol("g" + elem.part_id, positive=True) mna[elem.n1, elem.n1] = mna[elem.n1, elem.n1] + gd mna[elem.n1, elem.n2] = mna[elem.n1, elem.n2] - gd mna[elem.n2, elem.n1] = mna[elem.n2, elem.n1] - gd mna[elem.n2, elem.n2] = mna[elem.n2, elem.n2] + gd elif isinstance(elem, devices.InductorCoupling): pass # this is taken care of within the inductors elif circuit.is_elem_voltage_defined(elem): pass # we'll add its lines afterwards elif verbose: printing.print_warning("Skipped elem %s: not implemented." % (elem.part_id.upper(), )) pre_vde = mna.shape[0] for elem in circ: if circuit.is_elem_voltage_defined(elem): index = mna.shape[0] # get_matrix_size(mna)[0] mna = expand_matrix(mna, add_a_row=True, add_a_col=True) N = expand_matrix(N, add_a_row=True, add_a_col=False) # KCL mna[elem.n1, index] = +1 mna[elem.n2, index] = -1 # KVL mna[index, elem.n1] = +1 mna[index, elem.n2] = -1 if isinstance(elem, devices.VSource): if elem.is_symbolic: VDC = sympy.Symbol(elem.part_id.upper(), real=True) else: VDC = elem.dc_value N[index, 0] = -VDC elif isinstance(elem, devices.EVSource): if elem.is_symbolic: alpha = sympy.Symbol(elem.part_id.upper(), real=True) else: alpha = elem.alpha mna[index, elem.sn1] = -alpha mna[index, elem.sn2] = +alpha elif isinstance(elem, devices.Inductor): if ac: if elem.is_symbolic: L = sympy.Symbol(elem.part_id.upper(), real=True, positive=True) else: L = elem.L mna[index, index] = -s * L else: pass # already so: commented out # N[index,0] = 0 elif isinstance(elem, devices.HVSource): printing.print_warning( "symbolic.py: BUG - hvsources are not implemented yet.") sys.exit(33) for elem in circ: if circuit.is_elem_voltage_defined(elem): if isinstance(elem, devices.Inductor): if ac: # find its index to know which column corresponds to its # current this_index = circ.find_vde_index(elem.part_id, verbose=0) for cd in elem.coupling_devices: if cd.is_symbolic: M = sympy.Symbol(cd.part_id, real=True, positive=True) else: M = cd.K # get `part_id` of the other inductor (eg. "L32") other_id_wdescr = cd.get_other_inductor(elem.part_id) # find its index to know which column corresponds to # its current other_index = circ.find_vde_index(other_id_wdescr, verbose=0) # add the term. mna[pre_vde + this_index, pre_vde + other_index] += -s * M else: pass # all done return (mna, N, subs_g)
def mdn_solver( x, mna, circ, T, MAXIT, nv, locked_nodes, time=None, print_steps=False, vector_norm=lambda v: max(abs(v)), debug=True, ): """ Solves a problem like F(x) = 0 using the Newton Algorithm with a variable damping td. Where: F(x) = mna*x + T + T(x) mna is the Modified Network Analysis matrix of the circuit T(x) is the contribute of nonlinear elements to KCL T -> independent sources, time invariant and invariant x is the initial guess. Every x is given by: x = x + td*dx Where td is a damping coefficient to avoid overflow in non-linear components and excessive oscillation in the very first iteration. Afterwards td=1 To calculate td, an array of locked nodes is needed. The convergence check is done this way: if (vector_norm(dx) < alpha*vector_norm(x) + beta) and (vector_norm(residuo) < alpha*vector_norm(x) + beta): beta should be the machine precision, alpha 1e-(N+1) where N is the number of the significative digits you wish to have. Parameters: x: the initial guess. If set to None, it will be initialized to all zeros. Specifying a initial guess may improve the convergence time of the algorithm and determine which solution (if any) is found if there are more than one. mna: the Modified Network Analysis matrix of the circuit, reduced, see above element_list: T: see above. MAXIT: Maximum iterations that the method may perform. nv: number of nodes in the circuit (counting the ref, 0) locked_nodes: see get_td() and dc_solve(), generated by circ.get_locked_nodes() time: the value of time to be passed to non_linear _and_ time variant elements. print_steps: show a progress indicator vector_norm: Returns a tuple with: the solution, the remaining error, a boolean that is true whenever the method exits because of a successful convergence check the number of NR iterations performed """ # OLD COMMENT: FIXME REWRITE: solve through newton # problem is F(x)= mna*x +H(x) = 0 # H(x) = N + T(x) # lets say: J = dF/dx = mna + dT(x)/dx # J*dx = -1*(mna*x+N+T(x)) # dT/dx � lo jacobiano -> g_eq (o gm) # print_steps = False # locked_nodes = get_locked_nodes(element_list) mna_size = mna.shape[0] nonlinear_circuit = circ.is_nonlinear() tick = ticker.ticker(increments_for_step=1) tick.display(print_steps) if x is None: x = numpy.mat(numpy.zeros((mna_size, 1))) # if no guess was specified, its all zeros else: if not x.shape[0] == mna_size: raise Exception, "x0s size is different from expected: " + str(x.shape[0]) + " " + str(mna_size) if T is None: printing.print_warning("dc_analysis.mdn_solver called with T==None, setting T=0. BUG or no sources in circuit?") T = numpy.mat(numpy.zeros((mna_size, 1))) converged = False iteration = 0 for iteration in xrange(MAXIT): # newton iteration counter tick.step(print_steps) if nonlinear_circuit: # build dT(x)/dx (stored in J) and Tx(x) J, Tx = build_J_and_Tx(x, mna_size, circ.elements, time) J = J + mna else: J = mna Tx = 0 residuo = mna * x + T + Tx dx = numpy.linalg.inv(J) * (-1 * residuo) x = x + get_td(dx, locked_nodes, n=iteration) * dx if iteration > 0: if convergence_check(x, dx, residuo, nv - 1)[0]: converged = True break if vector_norm(dx) is numpy.nan: # Overflow raise OverflowError tick.hide(print_steps) if debug and not converged: convergence_by_node = convergence_check(x, dx, residuo, nv - 1, debug=True)[1] else: convergence_by_node = [] return (x, residuo, converged, iteration + 1, convergence_by_node)
def set_temperature(T): T = float(T) if T > 300: printing.print_warning(u"The temperature will be set to %f \xB0 C.") constants.T = utilities.Celsius2Kelvin(T)
def transient_analysis(circ, tstart, tstep, tstop, method=TRAP, x0=None, mna=None, N=None, \ D=None, data_filename="stdout", use_step_control=True, return_req_dict=None, verbose=3): """Performs a transient analysis of the circuit described by circ. Important parameters: - tstep is the maximum step to be allowed during simulation. - print_step_and_lte is a boolean value. Is set to true, the step and the LTE of the first element of x will be printed out to step_and_lte.graph in the current directory. """ if data_filename == "stdout": verbose = 0 _debug = False if _debug: print_step_and_lte = True else: print_step_and_lte = False HMAX = tstep #check parameters if tstart > tstop: printing.print_general_error("tstart > tstop") sys.exit(1) if tstep < 0: printing.print_general_error("tstep < 0") sys.exit(1) if verbose > 4: tmpstr = "Vea = %g Ver = %g Iea = %g Ier = %g max_time_iter = %g HMIN = %g" % \ (options.vea, options.ver, options.iea, options.ier, options.transient_max_time_iter, options.hmin) printing.print_info_line((tmpstr, 5), verbose) locked_nodes = circ.get_locked_nodes() if print_step_and_lte: flte = open("step_and_lte.graph", "w") flte.write("#T\tStep\tLTE\n") printing.print_info_line(("Starting transient analysis: ", 3), verbose) printing.print_info_line(("Selected method: %s" % (method,), 3), verbose) #It's a good idea to call transient with prebuilt MNA and N matrix #the analysis will be slightly faster (long netlists). if mna is None or N is None: (mna, N) = dc_analysis.generate_mna_and_N(circ) mna = utilities.remove_row_and_col(mna) N = utilities.remove_row(N, rrow=0) elif not mna.shape[0] == N.shape[0]: printing.print_general_error("mna matrix and N vector have different number of columns.") sys.exit(0) if D is None: # if you do more than one tran analysis, output streams should be changed... # this needs to be fixed D = generate_D(circ, [mna.shape[0], mna.shape[0]]) D = utilities.remove_row_and_col(D) # setup x0 if x0 is None: printing.print_info_line(("Generating x(t=%g) = 0" % (tstart,), 5), verbose) x0 = numpy.matrix(numpy.zeros((mna.shape[0], 1))) opsol = results.op_solution(x=x0, error=x0, circ=circ, outfile=None) else: if isinstance(x0, results.op_solution): opsol = x0 x0 = x0.asmatrix() else: opsol = results.op_solution(x=x0, error=numpy.matrix(numpy.zeros((mna.shape[0], 1))), circ=circ, outfile=None) printing.print_info_line(("Using the supplied op as x(t=%g)." % (tstart,), 5), verbose) if verbose > 4: print "x0:" opsol.print_short() # setup the df method printing.print_info_line(("Selecting the appropriate DF ("+method+")... ", 5), verbose, print_nl=False) if method == IMPLICIT_EULER: import implicit_euler as df elif method == TRAP: import trap as df elif method == GEAR1: import gear as df df.order = 1 elif method == GEAR2: import gear as df df.order = 2 elif method == GEAR3: import gear as df df.order = 3 elif method == GEAR4: import gear as df df.order = 4 elif method == GEAR5: import gear as df df.order = 5 elif method == GEAR6: import gear as df df.order = 6 else: df = import_custom_df_module(method, print_out=(data_filename != "stdout")) # df is none if module is not found if df is None: sys.exit(23) if not df.has_ff() and use_step_control: printing.print_warning("The chosen DF does not support step control. Turning off the feature.") use_step_control = False #use_aposteriori_step_control = False printing.print_info_line(("done.", 5), verbose) # setup the data buffer # if you use the step control, the buffer has to be one point longer. # That's because the excess point is used by a FF in the df module to predict the next value. printing.print_info_line(("Setting up the buffer... ", 5), verbose, print_nl=False) ((max_x, max_dx), (pmax_x, pmax_dx)) = df.get_required_values() if max_x is None and max_dx is None: printing.print_general_error("df doesn't need any value?") sys.exit(1) if use_step_control: thebuffer = dfbuffer(length=max(max_x, max_dx, pmax_x, pmax_dx) + 1, width=3) else: thebuffer = dfbuffer(length=max(max_x, max_dx) + 1, width=3) thebuffer.add((tstart, x0, None)) #setup the first values printing.print_info_line(("done.", 5), verbose) #FIXME #setup the output buffer if return_req_dict: output_buffer = dfbuffer(length=return_req_dict["points"], width=1) output_buffer.add((x0,)) else: output_buffer = None # import implicit_euler to be used in the first iterations # this is because we don't have any dx when we start, nor any past point value if (max_x is not None and max_x > 0) or max_dx is not None: import implicit_euler printing.print_info_line(("MNA (reduced):", 5), verbose) printing.print_info_line((str(mna), 5), verbose) printing.print_info_line(("D (reduced):", 5), verbose) printing.print_info_line((str(D), 5), verbose) # setup the initial values to start the iteration: x = None time = tstart nv = len(circ.nodes_dict) Gmin_matrix = dc_analysis.build_gmin_matrix(circ, options.gmin, mna.shape[0], verbose) # lo step viene generato automaticamente, ma non superare mai quello fornito. if use_step_control: #tstep = min((tstop-tstart)/9999.0, HMAX, 100.0 * options.hmin) tstep = min((tstop-tstart)/9999.0, HMAX) printing.print_info_line(("Initial step: %g"% (tstep,), 5), verbose) if max_dx is None: max_dx_plus_1 = None else: max_dx_plus_1 = max_dx +1 if pmax_dx is None: pmax_dx_plus_1 = None else: pmax_dx_plus_1 = pmax_dx +1 # setup error vectors aerror = numpy.mat(numpy.zeros((x0.shape[0], 1))) aerror[:nv-1, 0] = options.vea aerror[nv-1:, 0] = options.vea rerror = numpy.mat(numpy.zeros((x0.shape[0], 1))) rerror[:nv-1, 0] = options.ver rerror[nv-1:, 0] = options.ier iter_n = 0 # contatore d'iterazione lte = None sol = results.tran_solution(circ, tstart, tstop, op=x0, method=method, outfile=data_filename) printing.print_info_line(("Solving... ", 3), verbose, print_nl=False) tick = ticker.ticker(increments_for_step=1) tick.display(verbose > 1) while time < tstop: if iter_n < max(max_x, max_dx_plus_1): x_coeff, const, x_lte_coeff, prediction, pred_lte_coeff = \ implicit_euler.get_df((thebuffer.get_df_vector()[0],), tstep, \ predict=(use_step_control and (iter_n >= max(pmax_x, pmax_dx_plus_1)))) else: [x_coeff, const, x_lte_coeff, prediction, pred_lte_coeff] = \ df.get_df(thebuffer.get_df_vector(), tstep, predict=use_step_control) if options.transient_prediction_as_x0 and use_step_control and prediction is not None: x0 = prediction elif x is not None: x0 = x (x1, error, solved, n_iter) = dc_analysis.dc_solve(mna=(mna + numpy.multiply(x_coeff, D)) , Ndc=N, Ntran=D*const, circ=circ, Gmin=Gmin_matrix, x0=x0, time=(time + tstep), locked_nodes=locked_nodes, MAXIT=options.transient_max_nr_iter, verbose=0) if solved: old_step = tstep #we will modify it, if we're using step control otherwise it's the same # step control (yeah) if use_step_control: if x_lte_coeff is not None and pred_lte_coeff is not None and prediction is not None: # this is the Local Truncation Error :) lte = abs((x_lte_coeff / (pred_lte_coeff - x_lte_coeff)) * (prediction - x1)) # it should NEVER happen that new_step > 2*tstep, for stability new_step_coeff = 2 for index in xrange(x.shape[0]): if lte[index, 0] != 0: new_value = ((aerror[index, 0] + rerror[index, 0]*abs(x[index, 0])) / lte[index, 0]) \ ** (1.0 / (df.order+1)) if new_value < new_step_coeff: new_step_coeff = new_value #print new_value new_step = tstep * new_step_coeff if options.transient_use_aposteriori_step_control and new_step < options.transient_aposteriori_step_threshold * tstep: #don't recalculate a x for a small change tstep = check_step(new_step, time, tstop, HMAX) #print "Apost. (reducing) step = "+str(tstep) continue tstep = check_step(new_step, time, tstop, HMAX) # used in the next iteration #print "Apriori tstep = "+str(tstep) else: #print "LTE not calculated." lte = None if print_step_and_lte and lte is not None: #if you wish to look at the step. We print just a lte flte.write(str(time)+"\t"+str(old_step)+"\t"+str(lte.max())+"\n") # if we get here, either aposteriori_step_control is # disabled, or it's enabled and the error is small # enough. Anyway, the result is GOOD, STORE IT. time = time + old_step x = x1 iter_n = iter_n + 1 sol.add_line(time, x) dxdt = numpy.multiply(x_coeff, x) + const thebuffer.add((time, x, dxdt)) if output_buffer is not None: output_buffer.add((x, )) tick.step(verbose > 1) else: # If we get here, Newton failed to converge. We need to reduce the step... if use_step_control: tstep = tstep/5.0 tstep = check_step(tstep, time, tstop, HMAX) printing.print_info_line(("At %g s reducing step: %g s (convergence failed)" % (time, tstep), 5), verbose) else: #we can't reduce the step printing.print_general_error("Can't converge with step "+str(tstep)+".") printing.print_general_error("Try setting --t-max-nr to a higher value or set step to a lower one.") solved = False break if options.transient_max_time_iter and iter_n == options.transient_max_time_iter: printing.print_general_error("MAX_TIME_ITER exceeded ("+str(options.transient_max_time_iter)+"), iteration halted.") solved = False break if print_step_and_lte: flte.close() tick.hide(verbose > 1) if solved: printing.print_info_line(("done.", 3), verbose) printing.print_info_line(("Average time step: %g" % ((tstop - tstart)/iter_n,), 3), verbose) if output_buffer: ret_value = output_buffer.get_as_matrix() else: ret_value = sol else: print "failed." ret_value = None return ret_value
def solve(circ, tf_source=None, subs=None, opts=None, verbose=3): """Attempt a symbolic solution of the circuit. circ: the circuit instance to be simulated. tf_source: the name (string) of the source to be used as input for the transfer function. If None, no transfer function is evaluated. subs: a dictionary of sympy Symbols to be substituted. It makes solving the circuit easier. Eg. {R1:R2} - replace R1 with R2. It can be generated with parse_substitutions() opts: dict of 'option':boolean to be taken into account in simulation. currently 'r0s' and 'ac' are the only options considered. verbose: verbosity level 0 (silent) to 6 (painful). Returns: a dictionary with the solutions. """ if opts is None: # load the defaults opts = {'r0s':True, 'ac':False} if not 'r0s' in opts.keys(): opts.update({'r0s':True}) if not 'ac' in opts.keys(): opts.update({'ac':False}) if subs is None: subs = {} # no subs by default if not opts['ac']: printing.print_info_line(("Starting symbolic DC analysis...", 1), verbose) else: printing.print_info_line(("Starting symbolic AC analysis...", 1), verbose) printing.print_info_line(("Building symbolic MNA, N and x...", 3), verbose, print_nl=False) mna, N, subs_g = generate_mna_and_N(circ, opts, opts['ac']) x = get_variables(circ) mna = mna[1:, 1:] N = N[1:, :] printing.print_info_line((" done.", 3), verbose) printing.print_info_line(("Performing variable substitutions...", 5), verbose) mna, N = apply_substitutions(mna, N, subs) printing.print_info_line(("MNA matrix (reduced):", 5), verbose) if verbose > 5: print sympy.sstr(mna) printing.print_info_line(("N matrix (reduced):", 5), verbose) if verbose > 5: print sympy.sstr(N) printing.print_info_line(("Building equations...", 3), verbose) eq = [] for i in to_real_list(mna * x + N): eq.append(sympy.Eq(i, 0)) x = to_real_list(x) if verbose > 4: printing.print_symbolic_equations(eq) print "To be solved for:" print x #print "Matrix is singular: ", (mna.det() == 0) #print -1.0*mna.inv()*N #too heavy #print sympy.solve_linear_system(mna.row_join(-N), x) printing.print_info_line(("Performing auxiliary simplification...", 3), verbose) eq, x, sol_h = help_the_solver(eq, x) if len(eq): if verbose > 3: print "Symplified sytem:" printing.print_symbolic_equations(eq) print "To be solved for:" print x printing.print_info_line(("Solving...", 1), verbose) if options.symb_internal_solver: sol = local_solve(eq, x) else: sol = sympy.solve(eq, x, manual=options.symb_sympy_manual_solver, simplify=True) if sol is not None: sol.update(sol_h) else: sol = sol_h else: printing.print_info_line(("Auxiliary simplification solved the problem.", 3), verbose) sol = sol_h for ks in sol.keys(): sol.update({ks:sol[ks].subs(subs_g)}) #sol = sol_to_dict(sol, x) if sol == {}: printing.print_warning("No solutions. Check the netlist.") else: printing.print_info_line(("Success!", 2), verbose) if verbose > 1: print "Results:" printing.print_symbolic_results(sol) if tf_source is not None: src = sympy.Symbol(tf_source.upper(), real=True) printing.print_info_line(("Calculating small-signal symbolic transfer functions (%s))..."%(str(src),), 2), verbose, print_nl=False) tfs = calculate_gains(sol, src) printing.print_info_line(("done.", 2), verbose) printing.print_info_line(("Small-signal symbolic transfer functions:", 1), verbose) printing.print_symbolic_transfer_functions(tfs) else: tfs = None # convert to a results instance sol = results.symbolic_solution(sol, subs, circ) return sol, tfs
def symbolic_analysis(circ, source=None, ac_enable=True, r0s=False, subs=None, outfile=None, verbose=3): """Attempt a symbolic solution of the circuit. circ: the circuit instance to be simulated. source: the name (string) of the source to be used as input for the transfer function. If None, no transfer function is evaluated. ac_enable: take frequency dependency into consideration (default: True) r0s: take transistors' output impedance into consideration (default: False) subs: a dictionary of sympy Symbols to be substituted. It makes solving the circuit easier. Eg. {R1:R2} - replace R1 with R2. It can be generated with parse_substitutions() outfile: output filename ('stdout' means print to stdout). verbose: verbosity level 0 (silent) to 6 (painful). Returns: a dictionary with the solutions. """ if subs is None: subs = {} # no subs by default if not ac_enable: printing.print_info_line(("Starting symbolic DC analysis...", 1), verbose) else: printing.print_info_line(("Starting symbolic AC analysis...", 1), verbose) printing.print_info_line(("Building symbolic MNA, N and x...", 3), verbose, print_nl=False) mna, N, subs_g = generate_mna_and_N(circ, opts={'r0s': r0s}, ac=ac_enable, verbose=verbose) x = get_variables(circ) mna = mna[1:, 1:] N = N[1:, :] printing.print_info_line((" done.", 3), verbose) printing.print_info_line(("Performing variable substitutions...", 5), verbose) mna, N = apply_substitutions(mna, N, subs) printing.print_info_line(("MNA matrix (reduced):", 5), verbose) printing.print_info_line((sympy.sstr(mna), 5), verbose) printing.print_info_line(("N matrix (reduced):", 5), verbose) printing.print_info_line((sympy.sstr(N), 5), verbose) printing.print_info_line(("Building equations...", 3), verbose) eq = [] for i in to_real_list(mna * x + N): eq.append(sympy.Eq(i, 0)) x = to_real_list(x) if verbose > 4: printing.print_symbolic_equations(eq) print "To be solved for:" print x # print "Matrix is singular: ", (mna.det() == 0) # print -1.0*mna.inv()*N #too heavy # print sympy.solve_linear_system(mna.row_join(-N), x) printing.print_info_line(("Performing auxiliary simplification...", 3), verbose) eq, x, sol_h = help_the_solver(eq, x) if len(eq): printing.print_info_line(("Symplified sytem:", 3), verbose) if verbose > 3: printing.print_symbolic_equations(eq) printing.print_info_line(("To be solved for:", 3), verbose) printing.print_info_line((str(x), 3), verbose) printing.print_info_line(("Solving...", 1), verbose) if options.symb_internal_solver: sol = local_solve(eq, x) else: sol = sympy.solve(eq, x, manual=options.symb_sympy_manual_solver, simplify=True) if sol is not None: sol.update(sol_h) else: sol = sol_h else: printing.print_info_line( ("Auxiliary simplification solved the problem.", 3), verbose) sol = sol_h for ks in sol.keys(): sol.update({ks: sol[ks].subs(subs_g)}) # sol = sol_to_dict(sol, x) if sol == {}: printing.print_warning("No solutions. Check the netlist.") else: printing.print_info_line(("Success!", 2), verbose) printing.print_info_line(("Results:", 1), verbose) if options.cli: printing.print_symbolic_results(sol) if source is not None: src = sympy.Symbol(source.upper(), real=True) printing.print_info_line( ("Calculating small-signal symbolic transfer functions (%s))..." % (str(src), ), 2), verbose, print_nl=False) tfs = calculate_gains(sol, src) printing.print_info_line(("done.", 2), verbose) printing.print_info_line( ("Small-signal symbolic transfer functions:", 1), verbose) if options.cli: printing.print_symbolic_transfer_functions(tfs) else: tfs = None # convert to a results instance sol = results.symbolic_solution(sol, subs, circ, outfile) if tfs: if outfile and outfile != 'stdout': outfile += ".tfs" tfs = results.symbolic_solution(tfs, subs, circ, outfile, tf=True) return sol, tfs
def transient_analysis(circ, tstart, tstep, tstop, method=TRAP, x0=None, mna=None, N=None, \ D=None, data_filename="stdout", use_step_control=True, return_req_dict=None, verbose=3): """Performs a transient analysis of the circuit described by circ. Important parameters: - tstep is the maximum step to be allowed during simulation. - print_step_and_lte is a boolean value. Is set to true, the step and the LTE of the first element of x will be printed out to step_and_lte.graph in the current directory. """ if data_filename == "stdout": verbose = 0 _debug = False if _debug: print_step_and_lte = True else: print_step_and_lte = False HMAX = tstep #check parameters if tstart > tstop: printing.print_general_error("tstart > tstop") sys.exit(1) if tstep < 0: printing.print_general_error("tstep < 0") sys.exit(1) if verbose > 4: tmpstr = "Vea = %g Ver = %g Iea = %g Ier = %g max_time_iter = %g HMIN = %g" % \ (options.vea, options.ver, options.iea, options.ier, options.transient_max_time_iter, options.hmin) printing.print_info_line((tmpstr, 5), verbose) locked_nodes = circ.get_locked_nodes() if print_step_and_lte: flte = open("step_and_lte.graph", "w") flte.write("#T\tStep\tLTE\n") printing.print_info_line(("Starting transient analysis: ", 3), verbose) printing.print_info_line(("Selected method: %s" % (method, ), 3), verbose) #It's a good idea to call transient with prebuilt MNA and N matrix #the analysis will be slightly faster (long netlists). if mna is None or N is None: (mna, N) = dc_analysis.generate_mna_and_N(circ) mna = utilities.remove_row_and_col(mna) N = utilities.remove_row(N, rrow=0) elif not mna.shape[0] == N.shape[0]: printing.print_general_error( "mna matrix and N vector have different number of columns.") sys.exit(0) if D is None: # if you do more than one tran analysis, output streams should be changed... # this needs to be fixed D = generate_D(circ, [mna.shape[0], mna.shape[0]]) D = utilities.remove_row_and_col(D) # setup x0 if x0 is None: printing.print_info_line(("Generating x(t=%g) = 0" % (tstart, ), 5), verbose) x0 = numpy.matrix(numpy.zeros((mna.shape[0], 1))) opsol = results.op_solution(x=x0, error=x0, circ=circ, outfile=None) else: if isinstance(x0, results.op_solution): opsol = x0 x0 = x0.asmatrix() else: opsol = results.op_solution(x=x0, error=numpy.matrix( numpy.zeros((mna.shape[0], 1))), circ=circ, outfile=None) printing.print_info_line( ("Using the supplied op as x(t=%g)." % (tstart, ), 5), verbose) if verbose > 4: print "x0:" opsol.print_short() # setup the df method printing.print_info_line( ("Selecting the appropriate DF (" + method + ")... ", 5), verbose, print_nl=False) if method == IMPLICIT_EULER: import implicit_euler as df elif method == TRAP: import trap as df elif method == GEAR1: import gear as df df.order = 1 elif method == GEAR2: import gear as df df.order = 2 elif method == GEAR3: import gear as df df.order = 3 elif method == GEAR4: import gear as df df.order = 4 elif method == GEAR5: import gear as df df.order = 5 elif method == GEAR6: import gear as df df.order = 6 else: df = import_custom_df_module(method, print_out=(data_filename != "stdout")) # df is none if module is not found if df is None: sys.exit(23) if not df.has_ff() and use_step_control: printing.print_warning( "The chosen DF does not support step control. Turning off the feature." ) use_step_control = False #use_aposteriori_step_control = False printing.print_info_line(("done.", 5), verbose) # setup the data buffer # if you use the step control, the buffer has to be one point longer. # That's because the excess point is used by a FF in the df module to predict the next value. printing.print_info_line(("Setting up the buffer... ", 5), verbose, print_nl=False) ((max_x, max_dx), (pmax_x, pmax_dx)) = df.get_required_values() if max_x is None and max_dx is None: printing.print_general_error("df doesn't need any value?") sys.exit(1) if use_step_control: thebuffer = dfbuffer(length=max(max_x, max_dx, pmax_x, pmax_dx) + 1, width=3) else: thebuffer = dfbuffer(length=max(max_x, max_dx) + 1, width=3) thebuffer.add((tstart, x0, None)) #setup the first values printing.print_info_line(("done.", 5), verbose) #FIXME #setup the output buffer if return_req_dict: output_buffer = dfbuffer(length=return_req_dict["points"], width=1) output_buffer.add((x0, )) else: output_buffer = None # import implicit_euler to be used in the first iterations # this is because we don't have any dx when we start, nor any past point value if (max_x is not None and max_x > 0) or max_dx is not None: import implicit_euler printing.print_info_line(("MNA (reduced):", 5), verbose) printing.print_info_line((str(mna), 5), verbose) printing.print_info_line(("D (reduced):", 5), verbose) printing.print_info_line((str(D), 5), verbose) # setup the initial values to start the iteration: x = None time = tstart nv = len(circ.nodes_dict) Gmin_matrix = dc_analysis.build_gmin_matrix(circ, options.gmin, mna.shape[0], verbose) # lo step viene generato automaticamente, ma non superare mai quello fornito. if use_step_control: #tstep = min((tstop-tstart)/9999.0, HMAX, 100.0 * options.hmin) tstep = min((tstop - tstart) / 9999.0, HMAX) printing.print_info_line(("Initial step: %g" % (tstep, ), 5), verbose) if max_dx is None: max_dx_plus_1 = None else: max_dx_plus_1 = max_dx + 1 if pmax_dx is None: pmax_dx_plus_1 = None else: pmax_dx_plus_1 = pmax_dx + 1 # setup error vectors aerror = numpy.mat(numpy.zeros((x0.shape[0], 1))) aerror[:nv - 1, 0] = options.vea aerror[nv - 1:, 0] = options.vea rerror = numpy.mat(numpy.zeros((x0.shape[0], 1))) rerror[:nv - 1, 0] = options.ver rerror[nv - 1:, 0] = options.ier iter_n = 0 # contatore d'iterazione lte = None sol = results.tran_solution(circ, tstart, tstop, op=x0, method=method, outfile=data_filename) printing.print_info_line(("Solving... ", 3), verbose, print_nl=False) tick = ticker.ticker(increments_for_step=1) tick.display(verbose > 1) while time < tstop: if iter_n < max(max_x, max_dx_plus_1): x_coeff, const, x_lte_coeff, prediction, pred_lte_coeff = \ implicit_euler.get_df((thebuffer.get_df_vector()[0],), tstep, \ predict=(use_step_control and (iter_n >= max(pmax_x, pmax_dx_plus_1)))) else: [x_coeff, const, x_lte_coeff, prediction, pred_lte_coeff] = \ df.get_df(thebuffer.get_df_vector(), tstep, predict=use_step_control) if options.transient_prediction_as_x0 and use_step_control and prediction is not None: x0 = prediction elif x is not None: x0 = x (x1, error, solved, n_iter) = dc_analysis.dc_solve(mna=(mna + numpy.multiply(x_coeff, D)), Ndc=N, Ntran=D * const, circ=circ, Gmin=Gmin_matrix, x0=x0, time=(time + tstep), locked_nodes=locked_nodes, MAXIT=options.transient_max_nr_iter, verbose=0) if solved: old_step = tstep #we will modify it, if we're using step control otherwise it's the same # step control (yeah) if use_step_control: if x_lte_coeff is not None and pred_lte_coeff is not None and prediction is not None: # this is the Local Truncation Error :) lte = abs((x_lte_coeff / (pred_lte_coeff - x_lte_coeff)) * (prediction - x1)) # it should NEVER happen that new_step > 2*tstep, for stability new_step_coeff = 2 for index in xrange(x.shape[0]): if lte[index, 0] != 0: new_value = ((aerror[index, 0] + rerror[index, 0]*abs(x[index, 0])) / lte[index, 0]) \ ** (1.0 / (df.order+1)) if new_value < new_step_coeff: new_step_coeff = new_value #print new_value new_step = tstep * new_step_coeff if options.transient_use_aposteriori_step_control and new_step < options.transient_aposteriori_step_threshold * tstep: #don't recalculate a x for a small change tstep = check_step(new_step, time, tstop, HMAX) #print "Apost. (reducing) step = "+str(tstep) continue tstep = check_step(new_step, time, tstop, HMAX) # used in the next iteration #print "Apriori tstep = "+str(tstep) else: #print "LTE not calculated." lte = None if print_step_and_lte and lte is not None: #if you wish to look at the step. We print just a lte flte.write( str(time) + "\t" + str(old_step) + "\t" + str(lte.max()) + "\n") # if we get here, either aposteriori_step_control is # disabled, or it's enabled and the error is small # enough. Anyway, the result is GOOD, STORE IT. time = time + old_step x = x1 iter_n = iter_n + 1 sol.add_line(time, x) dxdt = numpy.multiply(x_coeff, x) + const thebuffer.add((time, x, dxdt)) if output_buffer is not None: output_buffer.add((x, )) tick.step(verbose > 1) else: # If we get here, Newton failed to converge. We need to reduce the step... if use_step_control: tstep = tstep / 5.0 tstep = check_step(tstep, time, tstop, HMAX) printing.print_info_line( ("At %g s reducing step: %g s (convergence failed)" % (time, tstep), 5), verbose) else: #we can't reduce the step printing.print_general_error("Can't converge with step " + str(tstep) + ".") printing.print_general_error( "Try setting --t-max-nr to a higher value or set step to a lower one." ) solved = False break if options.transient_max_time_iter and iter_n == options.transient_max_time_iter: printing.print_general_error("MAX_TIME_ITER exceeded (" + str(options.transient_max_time_iter) + "), iteration halted.") solved = False break if print_step_and_lte: flte.close() tick.hide(verbose > 1) if solved: printing.print_info_line(("done.", 3), verbose) printing.print_info_line( ("Average time step: %g" % ((tstop - tstart) / iter_n, ), 3), verbose) if output_buffer: ret_value = output_buffer.get_as_matrix() else: ret_value = sol else: print "failed." ret_value = None return ret_value