public void AddElement(ICircuitElement elm) { if (!elements.Contains(elm)) { elements.Add(elm); nodeMesh.Add(new long[elm.getLeadCount()]); for (int x = 0; x < elm.getLeadCount(); x++) { nodeMesh[nodeMesh.Count - 1][x] = -1; } int e_ndx = elements.Count - 1; int m_ndx = nodeMesh.Count - 1; if (e_ndx != m_ndx) { throw new System.Exception("AddElement array length mismatch"); } } }
public void analyze() { if (elements.Count == 0) { return; } nodeList.Clear(); List <bool> internalList = new List <bool>(); Action <long, bool> pushNode = (id, isInternal) => { if (!nodeList.Contains(id)) { nodeList.Add(id); internalList.Add(isInternal); } }; #region //// Look for Voltage or Ground element //// // Search the circuit for a Ground, or Voltage sourcre ICircuitElement voltageElm = null; bool gotGround = false; bool gotRail = false; for (int i = 0; i != elements.Count; i++) { ICircuitElement elem = elements[i]; if (elem is Ground) { gotGround = true; break; } if (elem is VoltageInput) { gotRail = true; } if (elem is Voltage && voltageElm == null) { voltageElm = elem; } } // If no ground and no rails, then the voltage elm's first terminal is ground. if (!gotGround && !gotRail && voltageElm != null) { int elm_ndx = elements.IndexOf(voltageElm); long[] ndxs = nodeMesh[elm_ndx]; pushNode(ndxs[0], false); } else { // If the circuit contains a ground, rail, or voltage // element, push a temporary node to the node list. pushNode(snowflake.NextId(), false); } #endregion // At this point, there is 1 node in the list, the special `global ground` node. #region //// Nodes and Voltage Sources //// int vscount = 0; // Number of voltage sources for (int i = 0; i != elements.Count; i++) { ICircuitElement elm = elements[i]; int leads = elm.getLeadCount(); // If the leadCount reported by the element does // not match the number of leads allocated, // resize the array. if (leads != nodeMesh[i].Length) { long[] leadMap = nodeMesh[i]; Array.Resize(ref leadMap, leads); nodeMesh[i] = leadMap; } // For each lead in the element for (int leadX = 0; leadX != leads; leadX++) { long leadNode = nodeMesh[i][leadX]; // Id of the node leadX is connected too int node_ndx = nodeList.IndexOf(leadNode); // Index of the leadNode in the nodeList if (node_ndx == -1) { // If the nodeList doesn't contain the node, push it // onto the list and assign it's new index to the lead. elm.setLeadNode(leadX, nodeList.Count); pushNode(leadNode, false); } else { // Otherwise, assign the lead the index of // the node in the nodeList. elm.setLeadNode(leadX, node_ndx); if (leadNode == 0) { // if it's the ground node, make sure the // node voltage is 0, cause it may not get set later elm.setLeadVoltage(leadX, 0); // TODO: ?? } } } // Push an internal node onto the list for // each internal lead on the element. int internalLeads = elm.getInternalLeadCount(); for (int x = 0; x != internalLeads; x++) { elm.setLeadNode(leads + x, nodeList.Count); pushNode(snowflake.NextId(), true); } vscount += elm.getVoltageSourceCount(); } #endregion // == Creeate the voltageSources array. // Also determine if circuit is nonlinear. // VoltageSourceId -> ICircuitElement map voltageSources = new ICircuitElement[vscount]; vscount = 0; circuitNonLinear = false; //for(int i = 0; i != elements.Count; i++) { // ICircuitElement elem = elements[i]; foreach (ICircuitElement elem in elements) { if (elem.nonLinear()) { circuitNonLinear = true; } // Assign each votage source in the element a globally unique id, // (the index of the next open slot in voltageSources) for (int leadX = 0; leadX != elem.getVoltageSourceCount(); leadX++) { voltageSources[vscount] = elem; elem.setVoltageSource(leadX, vscount++); } } #region //// Matrix setup //// int matrixSize = nodeList.Count - 1 + vscount; // setup circuitMatrix circuitMatrix = new double[matrixSize][]; for (int z = 0; z < matrixSize; z++) { circuitMatrix[z] = new double[matrixSize]; } circuitRightSide = new double[matrixSize]; // setup origMatrix origMatrix = new double[matrixSize][]; for (int z = 0; z < matrixSize; z++) { origMatrix[z] = new double[matrixSize]; } origRightSide = new double[matrixSize]; // setup circuitRowInfo circuitRowInfo = new RowInfo[matrixSize]; for (int i = 0; i != matrixSize; i++) { circuitRowInfo[i] = new RowInfo(); } circuitPermute = new int[matrixSize]; circuitMatrixSize = circuitMatrixFullSize = matrixSize; circuitNeedsMap = false; #endregion // Stamp linear circuit elements. for (int i = 0; i != elements.Count; i++) { elements[i].stamp(this); } #region //// Determine nodes that are unconnected //// bool[] closure = new bool[nodeList.Count]; bool changed = true; closure[0] = true; while (changed) { changed = false; for (int i = 0; i != elements.Count; i++) { ICircuitElement ce = elements[i]; // loop through all ce's nodes to see if they are connected // to other nodes not in closure for (int leadX = 0; leadX < ce.getLeadCount(); leadX++) { if (!closure[ce.getLeadNode(leadX)]) { if (ce.leadIsGround(leadX)) { closure[ce.getLeadNode(leadX)] = changed = true; } continue; } for (int k = 0; k != ce.getLeadCount(); k++) { if (leadX == k) { continue; } int kn = ce.getLeadNode(k); if (ce.leadsAreConnected(leadX, k) && !closure[kn]) { closure[kn] = true; changed = true; } } } } if (changed) { continue; } // connect unconnected nodes for (int i = 0; i != nodeList.Count; i++) { if (!closure[i] && !internalList[i]) { //System.out.println("node " + i + " unconnected"); stampResistor(0, i, 1E8); closure[i] = true; changed = true; break; } } } #endregion #region //// Sanity checks //// for (int i = 0; i != elements.Count; i++) { ICircuitElement ce = elements[i]; // look for inductors with no current path if (ce is InductorElm) { FindPathInfo fpi = new FindPathInfo(this, FindPathInfo.PathType.INDUCT, ce, ce.getLeadNode(1)); // first try findPath with maximum depth of 5, to avoid slowdowns if (!fpi.findPath(ce.getLeadNode(0), 5) && !fpi.findPath(ce.getLeadNode(0))) { //System.out.println(ce + " no path"); ce.reset(); } } // look for current sources with no current path if (ce is CurrentSource) { FindPathInfo fpi = new FindPathInfo(this, FindPathInfo.PathType.INDUCT, ce, ce.getLeadNode(1)); if (!fpi.findPath(ce.getLeadNode(0))) { panic("No path for current source!", ce); } } // look for voltage source loops if ((ce is Voltage && ce.getLeadCount() == 2) || ce is Wire) { FindPathInfo fpi = new FindPathInfo(this, FindPathInfo.PathType.VOLTAGE, ce, ce.getLeadNode(1)); if (fpi.findPath(ce.getLeadNode(0))) { panic("Voltage source/wire loop with no resistance!", ce); } } // look for shorted caps, or caps w/ voltage but no R if (ce is CapacitorElm) { FindPathInfo fpi = new FindPathInfo(this, FindPathInfo.PathType.SHORT, ce, ce.getLeadNode(1)); if (fpi.findPath(ce.getLeadNode(0))) { //System.out.println(ce + " shorted"); ce.reset(); } else { fpi = new FindPathInfo(this, FindPathInfo.PathType.CAP_V, ce, ce.getLeadNode(1)); if (fpi.findPath(ce.getLeadNode(0))) { panic("Capacitor loop with no resistance!", ce); } } } } #endregion #region //// Simplify the matrix //// =D for (int i = 0; i != matrixSize; i++) { int qm = -1, qp = -1; double qv = 0; RowInfo re = circuitRowInfo[i]; if (re.lsChanges || re.dropRow || re.rsChanges) { continue; } double rsadd = 0; // look for rows that can be removed int leadX = 0; for (; leadX != matrixSize; leadX++) { double q = circuitMatrix[i][leadX]; if (circuitRowInfo[leadX].type == RowInfo.ROW_CONST) { // keep a running total of const values that have been removed already rsadd -= circuitRowInfo[leadX].value * q; continue; } if (q == 0) { continue; } if (qp == -1) { qp = leadX; qv = q; continue; } if (qm == -1 && q == -qv) { qm = leadX; continue; } break; } if (leadX == matrixSize) { if (qp == -1) { panic("Matrix error", null); } RowInfo elt = circuitRowInfo[qp]; if (qm == -1) { // we found a row with only one nonzero entry; // that value is a constant for (int k = 0; elt.type == RowInfo.ROW_EQUAL && k < 100; k++) { // follow the chain // System.out.println("following equal chain from " + i + " " + qp + " to " + elt.nodeEq); qp = elt.nodeEq; elt = circuitRowInfo[qp]; } if (elt.type == RowInfo.ROW_EQUAL) { // break equal chains // System.out.println("Break equal chain"); elt.type = RowInfo.ROW_NORMAL; continue; } if (elt.type != RowInfo.ROW_NORMAL) { //System.out.println("type already " + elt.type + " for " + qp + "!"); continue; } elt.type = RowInfo.ROW_CONST; elt.value = (circuitRightSide[i] + rsadd) / qv; circuitRowInfo[i].dropRow = true; // System.out.println(qp + " * " + qv + " = const " + elt.value); i = -1; // start over from scratch } else if (circuitRightSide[i] + rsadd == 0) { // we found a row with only two nonzero entries, and one // is the negative of the other; the values are equal if (elt.type != RowInfo.ROW_NORMAL) { // System.out.println("swapping"); int qq = qm; qm = qp; qp = qq; elt = circuitRowInfo[qp]; if (elt.type != RowInfo.ROW_NORMAL) { // we should follow the chain here, but this hardly // ever happens so it's not worth worrying about //System.out.println("swap failed"); continue; } } elt.type = RowInfo.ROW_EQUAL; elt.nodeEq = qm; circuitRowInfo[i].dropRow = true; // System.out.println(qp + " = " + qm); } } } // == Find size of new matrix int nn = 0; for (int i = 0; i != matrixSize; i++) { RowInfo elt = circuitRowInfo[i]; if (elt.type == RowInfo.ROW_NORMAL) { elt.mapCol = nn++; // System.out.println("col " + i + " maps to " + elt.mapCol); continue; } if (elt.type == RowInfo.ROW_EQUAL) { RowInfo e2 = null; // resolve chains of equality; 100 max steps to avoid loops for (int leadX = 0; leadX != 100; leadX++) { e2 = circuitRowInfo[elt.nodeEq]; if (e2.type != RowInfo.ROW_EQUAL) { break; } if (i == e2.nodeEq) { break; } elt.nodeEq = e2.nodeEq; } } if (elt.type == RowInfo.ROW_CONST) { elt.mapCol = -1; } } for (int i = 0; i != matrixSize; i++) { RowInfo elt = circuitRowInfo[i]; if (elt.type == RowInfo.ROW_EQUAL) { RowInfo e2 = circuitRowInfo[elt.nodeEq]; if (e2.type == RowInfo.ROW_CONST) { // if something is equal to a const, it's a const elt.type = e2.type; elt.value = e2.value; elt.mapCol = -1; } else { elt.mapCol = e2.mapCol; } } } // == Make the new, simplified matrix. int newsize = nn; double[][] newmatx = new double[newsize][]; for (int z = 0; z < newsize; z++) { newmatx[z] = new double[newsize]; } double[] newrs = new double[newsize]; int ii = 0; for (int i = 0; i != matrixSize; i++) { RowInfo rri = circuitRowInfo[i]; if (rri.dropRow) { rri.mapRow = -1; continue; } newrs[ii] = circuitRightSide[i]; rri.mapRow = ii; // System.out.println("Row " + i + " maps to " + ii); for (int leadX = 0; leadX != matrixSize; leadX++) { RowInfo ri = circuitRowInfo[leadX]; if (ri.type == RowInfo.ROW_CONST) { newrs[ii] -= ri.value * circuitMatrix[i][leadX]; } else { newmatx[ii][ri.mapCol] += circuitMatrix[i][leadX]; } } ii++; } #endregion #region //// Copy matrix to orig //// circuitMatrix = newmatx; circuitRightSide = newrs; matrixSize = circuitMatrixSize = newsize; // copy `rightSide` to `origRightSide` for (int i = 0; i != matrixSize; i++) { origRightSide[i] = circuitRightSide[i]; } // copy `matrix` to `origMatrix` for (int i = 0; i != matrixSize; i++) { for (int leadX = 0; leadX != matrixSize; leadX++) { origMatrix[i][leadX] = circuitMatrix[i][leadX]; } } #endregion circuitNeedsMap = true; _analyze = false; // If the matrix is linear, we can do the lu_factor // here instead of needing to do it every frame. if (!circuitNonLinear) { if (!lu_factor(circuitMatrix, circuitMatrixSize, circuitPermute)) { panic("Singular matrix!", null); } } }
public bool findPath(int n1, int depth) { if (n1 == dest) { return(true); } if (depth-- == 0) { return(false); } if (used[n1]) { return(false); } used[n1] = true; for (int i = 0; i != sim.elements.Count; i++) { ICircuitElement ce = sim.getElm(i); if (ce == firstElm) { continue; } if (type == PathType.INDUCT) { if (ce is CurrentSource) { continue; } } if (type == PathType.VOLTAGE) { if (!(ce.isWire() || ce is Voltage)) { continue; } } if (type == PathType.SHORT && !ce.isWire()) { continue; } if (type == PathType.CAP_V) { if (!(ce.isWire() || ce is CapacitorElm || ce is Voltage)) { continue; } } if (n1 == 0) { // look for posts which have a ground connection; // our path can go through ground for (int z = 0; z != ce.getLeadCount(); z++) { if (ce.leadIsGround(z) && findPath(ce.getLeadNode(z), depth)) { used[n1] = false; return(true); } } } int j; for (j = 0; j != ce.getLeadCount(); j++) { if (ce.getLeadNode(j) == n1) { break; } } if (j == ce.getLeadCount()) { continue; } if (ce.leadIsGround(j) && findPath(0, depth)) { // System.out.println(ce + " has ground"); used[n1] = false; return(true); } if (type == PathType.INDUCT && ce is InductorElm) { double c = ce.getCurrent(); if (j == 0) { c = -c; } // System.out.println("matching " + c + " to " + firstElm.getCurrent()); // System.out.println(ce + " " + firstElm); if (Math.Abs(c - firstElm.getCurrent()) > 1e-10) { continue; } } for (int k = 0; k != ce.getLeadCount(); k++) { if (j == k) { continue; } // System.out.println(ce + " " + ce.getNode(j) + "-" + ce.getNode(k)); if (ce.leadsAreConnected(j, k) && findPath(ce.getLeadNode(k), depth)) { // System.out.println("got findpath " + n1); used[n1] = false; return(true); } // System.out.println("back on findpath " + n1); } } used[n1] = false; // System.out.println(n1 + " failed"); return(false); }