/// <summary> /// Configures the <paramref name="source"/> for desired <paramref name="state"/>. /// Outputs is set to 1 in order to generate transfer functions. /// </summary> /// <param name="sourceIndex"></param> /// <param name="state">True if the source is active, false if not (it is considered as short-circuit)</param> private void ConfigureVoltageSource(AdmittanceMatrix matrix, ISourceDescription source, bool state) { // Get the voltage source's nodes var nodes = _SourcesNodes[source]; // And its index var sourceIndex = _IndexedComponentsIndices[source]; // If the positive terminal is not grounded if (nodes.Positive != ReferenceNode) { // Fill the entry in the row corresponding to the node and column corresponding to the source (plus start column) // with 1 (positive terminal) matrix._B[nodes.Positive, sourceIndex] = 1; // Fill the entry in the row corresponding to the source (plus starting row) // and column corresponding to the node with 1 (positive terminal) matrix._C[sourceIndex, nodes.Positive] = 1; } // If the negative terminal is not grounded if (nodes.Negative != ReferenceNode) { // Fill the entry in the row corresponding to the node and column corresponding to the source (plus start column) // with -1 (negative terminal) matrix._B[nodes.Negative, sourceIndex] = -1; // Fill the entry in the row corresponding to the source (plus starting row) // and column corresponding to the node with -1 (negative terminal) matrix._C[sourceIndex, nodes.Negative] = -1; } matrix._E[sourceIndex] = state ? source.OutputValue : 0; }
/// <summary> /// Activates (all current sources are not active by default) the current source given by the index. /// </summary> /// <param name="sourceIndex"></param> private void ActivateCurrentSource(AdmittanceMatrix matrix, ISourceDescription source) { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // Matrix I has += and -= instead of just assignment because that's the actual correct way of building the matrix. // // However it only matters if there is more than one source active at a time. For example, if there would be 2 sources, each connected // // to the same node, then the value in the corresponding entry in I matrix should be a sum of produced currents. Simply assigning value // // would result in an error. Again, for now, admittance matrices are built only for one source, however if it would happen that it changes // // in the future then there won't be any errors because of assigning rather than adding / subtracting. // // // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Get the nodes var nodes = _SourcesNodes[source]; // If the positive terminal is not grounded if (nodes.Positive != ReferenceNode) { // Add source's current to the node matrix._I[nodes.Positive] += source.OutputValue; } // If the negative terminal is not grounded if (nodes.Negative != -1) { // Subtract source's current from the node matrix._I[nodes.Negative] -= source.OutputValue; } }
/// <summary> /// Configures submatrices so that <paramref name="opAmp"/> is considered to work in saturation, however output remains at 0 - op-amps have /// to be manually activated, for example using <see cref="ActivateSaturatedOpAmp(AdmittanceMatrix, IOpAmpDescription)"/>. /// </summary> /// <param name="matrix"></param> /// <param name="opAmp"></param> private void ConfigureOpAmpForSaturation(AdmittanceMatrix matrix, IOpAmpDescription opAmp) { // Indices of op-amps nodes var nodes = _OpAmpsNodes[opAmp]; // And its index var index = _IndexedComponentsIndices[opAmp]; // If the non-inverting input is not grounded, reset its entry in the _C matrix if (nodes.NonInvertingInput != ReferenceNode) { matrix._C[index, nodes.NonInvertingInput] = 0; } // If the inverting input is not grounded, reset its entry in the _C matrix if (nodes.InvertingInput != ReferenceNode) { matrix._C[index, nodes.InvertingInput] = 0; } // And the entry in _C corresponding to the output node to 1 // It is important that, when non-inverting input is connected directly to the output, the entry in _B // corresponding to that node is 1 (and not 0 like the if above would set it). Because this assigning is done after // the one for non-inverting input no special conditions are necessary however it's very important to remeber about // it if (when) this method is modified matrix._C[index, nodes.Output] = 1; }
/// <summary> /// Fills the non-diagonal entries of an admittance matrix - for entry i,j all admittances located between node i and node j are subtracted /// from that entry /// </summary> private void FillAMatrixNonDiagonal(AdmittanceMatrix matrix, double frequency) { // For each node foreach (var node1 in _Nodes) { // Matrix A is symmetrical along the main diagonal so it's only necessary to fill the // part below main diagonal and copy the operation to the corresponding entry above the main diagonal foreach (var node2 in _Nodes.FindAll((x) => x.Index < node1.Index)) { // Find all components located between node i and node j var admittancesBetweenNodesij = new List <IBaseComponent>(node1.ConnectedComponents.Intersect(node2.ConnectedComponents)); // For each of them admittancesBetweenNodesij.ForEach((component) => { // If the component is a two terminal if (component is ITwoTerminal twoTerminal) { // Get the admittance of the element var admittance = twoTerminal.GetAdmittance(frequency); // Subtract its admittance from the matrix matrix._A[node1.Index, node2.Index] -= admittance; // And do the same to the entry j,i - admittances between node i,j are identical to admittances between nodes j,i matrix._A[node2.Index, node1.Index] -= admittance; } }); } } }
/// <summary> /// Configures all voltage sources for specific operation. /// </summary> private void ConfigureVoltageSources(AdmittanceMatrix matrix, bool state) { // Take all voltage sources (DC + AC) foreach (var source in _DCVoltageSources.Concat(_ACVoltageSources)) { // And configure them for the state ConfigureVoltageSource(matrix, source, state); } }
/// <summary> /// Fills the B part of admittance matrix with 0 or 1 based on op-amps present in the circuit /// </summary> private void FillBMatrixOpAmpOutputNodes(AdmittanceMatrix matrix) { // For each op-amp foreach (var opAmp in _OpAmps) { // Set the entry in _B corresponding to the output node to 1 matrix._B[_OpAmpsNodes[opAmp].Output, _IndexedComponentsIndices[opAmp]] = 1; } }
/// <summary> /// Modifies <paramref name="matrix"/> with initial op-amp settings - op-amps are set in their respective operation modes depending on /// <see cref="_OpAmpOperation"/>. /// </summary> private void InitialOpAmpConfiguration(AdmittanceMatrix matrix) { FillBMatrixOpAmpOutputNodes(matrix); // Configure each op-amp for its operation foreach (var opAmp in _OpAmps) { ConfigureOpAmpOperation(matrix, opAmp, _OpAmpOperation[opAmp]); } }
/// <summary> /// Initializes submatrices of the given <see cref="AdmittanceMatrix"/> /// </summary> /// <param name="matrix"></param> private void InitializeSubmatrices(AdmittanceMatrix matrix) { matrix._A = ArrayHelpers.CreateAndInitialize(Complex.Zero, _BigDimension, _BigDimension); matrix._B = ArrayHelpers.CreateAndInitialize <Complex>(0, _BigDimension, _SmallDimension); matrix._C = ArrayHelpers.CreateAndInitialize(Complex.Zero, _SmallDimension, _BigDimension); matrix._D = ArrayHelpers.CreateAndInitialize(Complex.Zero, _SmallDimension, _SmallDimension); matrix._E = ArrayHelpers.CreateAndInitialize(Complex.Zero, _SmallDimension); matrix._I = ArrayHelpers.CreateAndInitialize(Complex.Zero, _BigDimension); }
/// <summary> /// Activates all saturated op amps /// </summary> /// <param name="matrix"></param> private void ActivateSaturatedOpAmps(AdmittanceMatrix matrix) { // For each op-amp foreach (var opAmp in _OpAmps) { // If it's in either saturation if (_OpAmpOperation[opAmp] == OpAmpOperationMode.PositiveSaturation || _OpAmpOperation[opAmp] == OpAmpOperationMode.NegativeSaturation) { // Activate it ActivateSaturatedOpAmp(matrix, opAmp); } } }
/// <summary> /// Configures submatrices so that <paramref name="opAmp"/> is considered to work in active operation (output is between /// supply voltages) /// </summary> /// <param name="matrix"></param> /// <param name="opAmp"></param> private void ConfigureOpAmpForActiveOperation(AdmittanceMatrix matrix, IOpAmpDescription opAmp) { // Get nodes of the op-amp var nodes = _OpAmpsNodes[opAmp]; // As well as its index var index = _IndexedComponentsIndices[opAmp]; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // Very important: in case the non-inverting input and output are short-circuited (they are the same node) then // // the value of 1 should be entered into the corresponding cell in _C array. This comes from the fact that having // // -OpenLoopGain and OpenLoogGain in the _C in the same row enforces potentials at both inputs to be equal. However // // if the non-inverting input is shorted then the OpenLoopGain value has no place in _C and 1 from the output should // // be put there. // // If the inverting input is shorted with the output then the OpenLoopGain should be put int the corresponding cell // // instead of 1. That's because the initial assumption that V+ = V- is made and if Vout = V- (the same node) then // // The OpenLoopGain has to be used to guarantee both voltages will be equal // // (so matrix looks like : ... -k ... k ... | 0 which boils down to kV- = kV+ which means V- = V+) // // That's why it is very important that if (when) this method is modified the rules presented above are obeyed. // // // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // If there exists a node to which TerminalA (non-inverting input) is connected // (it's possible it may not exist due to removed ground node) if (nodes.NonInvertingInput != ReferenceNode) { // Fill the entry in the row corresponding to the op-amp (plus starting row) // and column corresponding to the node (positive terminal) with -OpenLoopGain matrix._C[index, nodes.NonInvertingInput] = -opAmp.OpenLoopGain; } // If there exists a node to which TerminalB (inverting input) is connected // (it's possible it may not exist due to removed ground node) if (nodes.InvertingInput != ReferenceNode) { // Fill the entry in the row corresponding to the op-amp (plus starting row) // and column corresponding to the node (positive terminal) with OpenLoopGain matrix._C[index, nodes.InvertingInput] = opAmp.OpenLoopGain; } // If the output is not shorted with the inverting input if (nodes.Output != nodes.InvertingInput) { matrix._C[index, nodes.Output] = 1; } // Fill the entry in the row corresponding to the op-amp (plus starting row) // and column corresponding to the node (positive terminal) with 1 matrix._E[index] = 0; }
/// <summary> /// Fills the diagonal of an admittance matrix - for i-th node adds all admittances connected to it, to the cell denoted by indices i,i /// </summary> private void FillAMatrixDiagonal(AdmittanceMatrix matrix, double frequency) { // For each node foreach (var node in _Nodes) { // For each component connected to that node node.ConnectedComponents.ForEach((component) => { // If the component is a two terminal if (component is ITwoTerminal twoTerminal) { // Add its admittance to the matrix matrix._A[node.Index, node.Index] += twoTerminal.GetAdmittance(frequency); } }); } }
/// <summary> /// Constructs and initializes the general version (without any source in mind) of admittance matrix /// </summary> /// <param name="frequency"></param> /// <returns></returns> private AdmittanceMatrix ConstructAndInitialize(double frequency) { // TODO: Short circuit inductors for DC when they're added var matrix = new AdmittanceMatrix(_BigDimension, _SmallDimension); // Initialize submatrices (arrays) InitializeSubmatrices(matrix); // Fill A matrix - it's only dependent on frequency FillAMatrix(matrix, frequency); // Configure voltage sources to be off - the only active voltage source (if any) can then be turned on ConfigureVoltageSources(matrix, false); // Initialize op-amp settings - disabled saturated op-amps InitialOpAmpConfiguration(matrix); return(matrix); }
/// <summary> /// Configures <see cref="IOpAmp"/> for operation in <paramref name="operationMode"/> in <paramref name="matrix"/>. Saturated op-amps remain /// inactive after this call - they will appear to be saturated but saturation voltage will be equal to 0. Op-amps have to be manually /// activated, for example using <see cref="ActivateSaturatedOpAmps(AdmittanceMatrix)"/> /// </summary> /// <param name="matrix"></param> /// <param name="opAmpIndex"></param> /// <param name="operationMode"></param> private void ConfigureOpAmpOperation(AdmittanceMatrix matrix, IOpAmpDescription opAmp, OpAmpOperationMode operationMode) { // Call appropriate method based on operation mode switch (_OpAmpOperation[opAmp]) { case OpAmpOperationMode.Active: { ConfigureOpAmpForActiveOperation(matrix, opAmp); } break; case OpAmpOperationMode.PositiveSaturation: case OpAmpOperationMode.NegativeSaturation: { ConfigureOpAmpForSaturation(matrix, opAmp); } break; default: { throw new Exception("Unhandled case"); } } }
/// <summary> /// Activates saturated op-amp - modifies matrix E with saturation voltage in entry corresponding to that op-amp. Does not check if /// the op-amp is in fact saturated - assumes that caller checked that. /// </summary> /// <param name="matrix"></param> /// <param name="opAmp"></param> private void ActivateSaturatedOpAmp(AdmittanceMatrix matrix, IOpAmpDescription opAmp) => // Depending on which supply was exceeded, set the value of the op-amp output to either positive or negative // supply voltage, depending on saturaiton (if determined that it is in either saturation). // Modify entry in E matrix corresponding to index of the op-amp. matrix._E[_IndexedComponentsIndices[opAmp]] = _OpAmpOperation[opAmp] == OpAmpOperationMode.PositiveSaturation ? opAmp.PositiveSupplyVoltage : opAmp.NegativeSupplyVoltage;
/// <summary> /// Fills the <see cref="_A"/> Matrix /// </summary> private void FillAMatrix(AdmittanceMatrix matrix, double frequency) { FillAMatrixDiagonal(matrix, frequency); FillAMatrixNonDiagonal(matrix, frequency); }