for this code give me test case to have the sin vo...
Erstellt am: 10. Juni 2025
Beantwortet mit O4 Mini High von Chat01
Erstellt am: 10. Juni 2025
Beantwortet mit O4 Mini High von Chat01
for this code give me test case to have the sin volt source and get the output of tran print in complete period
#include <bits/stdc++.h>
using namespace std;
/**************************************************************************************************
CONSTANTS
**************************************************************************************************/
static constexpr double DEFAULT_DIODE_DROP = 0.7; // ولتاژ افت دیود ایدهآل (۷۰۰ mV)
static constexpr double SMALL_RESISTANCE_R = 1e-3; // مقاومت خیلی کوچک (1 mΩ) برای دیود/سلف در DC
static constexpr double EPS = 1e-12; // برای تشخیص تکینبودن ماتریس
/**************************************************************************************************
UTILITY FUNCTIONS
************************************************************************************************/
/
/**
/**
/**
double multiplier = 1.0;
textif (str.size() >= 3 && str.substr(str.size() - 3) == "meg") { multiplier = 1e6; str = str.substr(0, str.size() - 3); } else if (!str.empty() && str.back() == 'g') { multiplier = 1e9; str.pop_back(); } else if (!str.empty() && str.back() == 'k') { multiplier = 1e3; str.pop_back(); } else if (!str.empty() && str.back() == 'm') { multiplier = 1e-3; str.pop_back(); } else if (!str.empty() && str.back() == 'u') { multiplier = 1e-6; str.pop_back(); } else if (!str.empty() && str.back() == 'n') { multiplier = 1e-9; str.pop_back(); } else if (!str.empty() && str.back() == 'p') { multiplier = 1e-12; str.pop_back(); } else if (!str.empty() && str.back() == 'f') { multiplier = 1e-15; str.pop_back(); } try { size_t idx; double base = stod(str, &idx); if (idx != str.size()) throw runtime_error(""); return base * multiplier; } catch (...) { throw runtime_error("Invalid numeric value: " + s); }
}
/**
/**************************************************************************************************
NODE MAPPING (STRING → INTEGER)
**************************************************************************************************/
struct NodeMap {
unordered_map<string,int> name2idx;
vector<string> idx2name;
textNodeMap() { // گره زمین با نام "0" معادل اندیس 0 name2idx["0"] = 0; idx2name.push_back("0"); } /** * getIdx: * اگر نام گره ثبتشده باشد اندیس آن را برمیگرداند، در غیر این صورت یک اندیس جدید اختصاص میدهد. */ int getIdx(const string &nodeName) { if (name2idx.count(nodeName)) return name2idx[nodeName]; int newIdx = (int)idx2name.size(); name2idx[nodeName] = newIdx; idx2name.push_back(nodeName); return newIdx; } bool hasName(const string &nodeName) const { return name2idx.count(nodeName); } int count() const { return (int)idx2name.size(); } /** * renameNode: * نام گره oldName را به newName تغییر میدهد. اگر oldName وجود نداشته باشد یا newName * تکراری باشد، خطا پرتاب میکند. */ void renameNode(const string &oldName, const string &newName) { if (!name2idx.count(oldName)) { throw runtime_error("Error: node '" + oldName + "' not found."); } if (name2idx.count(newName)) { throw runtime_error("Error: node name '" + newName + "' already exists."); } int idx = name2idx[oldName]; name2idx.erase(oldName); name2idx[newName] = idx; idx2name[idx] = newName; }
};
/**************************************************************************************************
ELEMENT BASE CLASS
**************************************************************************************************/
class Element {
public:
enum Type { RES, CAP, IND, DIO, VS, IS, VCVS, CCCS, CCVS, VCCS };
Element(Type t, string nm, int n1, int n2)
: type(t), name(std::move(nm)), nPlus(n1), nMinus(n2) {}
virtual ~Element() = default;
textType type; // نوع المان string name; // نام المان int nPlus; // اندیس گره مثبت یا اول int nMinus; // اندیس گره منفی یا دوم /** * stamp: * وظیفه دارد مهرگذاری المان در ماتریس A و بردار b را انجام دهد. * nNode تعداد گرههای غیر از زمین است و vsMap نگاشت نام منابع ولتاژ به اندیس آنهاست. */ virtual void stamp(vector<vector<double>> &A, vector<double> &b,int nNode, const unordered_map<string,int> &vsMap) const = 0; virtual void stampTransient(std::vector<std::vector<double>>& /*A*/, std::vector<double>& /*b*/, double /*dt*/, std::unordered_map<std::string,double>& /*capVPrev*/, std::unordered_map<std::string,double>& /*indIPrev*/, int /*nNode*/, const std::unordered_map<std::string,int>& /*vsMap*/) const { /* empty – override in C & L */ }
};
/**************************************************************************************************
CONCRETE ELEMENT CLASSES
**************************************************************************************************/
class Resistor : public Element {
public:
double R;
Resistor(string nm, int a, int b, double val)
: Element(Element::RES, std::move(nm), a, b), R(val) {
if (R <= 0.0) {
throw runtime_error("Error: Resistance must be > 0 (" + name + ").");
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> & /vsMap/) const override {
double g = 1.0 / R;
int i = nPlus, j = nMinus;
if (i != 0) A[i-1][i-1] += g;
if (j != 0) A[j-1][j-1] += g;
if (i != 0 && j != 0) {
A[i-1][j-1] -= g;
A[j-1][i-1] -= g;
}
}
};
class Capacitor : public Element {
public:
double C;
Capacitor(string nm, int a, int b, double val)
: Element(Element::CAP, std::move(nm), a, b), C(val) {
if (C < 0.0) {
throw runtime_error("Error: Capacitance must be ≥ 0 (" + name + ").");
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> & /vsMap/) const override {
// در DC مثل مدار باز → هیچ مهرگذاری انجام نمیشود.
}
void stampTransient(std::vector<std::vector<double>>& A,
std::vector<double>& b,
double dt,
std::unordered_mapstd::string,double& capVPrev,
std::unordered_mapstd::string,double& /indIPrev/,
int nNode,
const std::unordered_mapstd::string,int& /vsMap/) const override
{
double G = C / dt; // Norton conductance
double Vpr = capVPrev[name]; // V[n]
double Ieq = G * Vpr; // companion current source
textint i = nPlus, j = nMinus; if (i != 0) A[i-1][i-1] += G; if (j != 0) A[j-1][j-1] += G; if (i != 0 && j != 0) { A[i-1][j-1] -= G; A[j-1][i-1] -= G; } if (i != 0) b[i-1] += Ieq; if (j != 0) b[j-1] += -Ieq; }
};
class Inductor : public Element {
public:
double L;
Inductor(string nm, int a, int b, double val)
: Element(Element::IND, std::move(nm), a, b), L(val) {
if (L < 0.0) {
throw runtime_error("Error: Inductance must be ≥ 0 (" + name + ").");
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> & /vsMap/) const override {
// مدلسازی سلف در DC به کمک مقاومت کوچک سری
double g = 1.0 / SMALL_RESISTANCE_R;
int i = nPlus, j = nMinus;
if (i != 0) A[i-1][i-1] += g;
if (j != 0) A[j-1][j-1] += g;
if (i != 0 && j != 0) {
A[i-1][j-1] -= g;
A[j-1][i-1] -= g;
}
}
void stampTransient(std::vector<std::vector<double>>& A,
std::vector<double>& b,
double dt,
std::unordered_mapstd::string,double& /capVPrev/,
std::unordered_mapstd::string,double& indIPrev,
int nNode,
const std::unordered_mapstd::string,int& /vsMap/) const override
{
double G = dt / L; // Norton conductance (G = 1/R)
double Ipr = indIPrev[name]; // I[n]
// Ieq is simply previous current (Norton current source)
double Ieq = Ipr;
textint i = nPlus, j = nMinus; if (i != 0) A[i-1][i-1] += G; if (j != 0) A[j-1][j-1] += G; if (i != 0 && j != 0) { A[i-1][j-1] -= G; A[j-1][i-1] -= G; } if (i != 0) b[i-1] += -Ieq; // sign is opposite of capacitor! if (j != 0) b[j-1] += Ieq; }
};
class Diode : public Element {
public:
string model;
Diode(string nm, int a, int b, string mdl)
: Element(Element::DIO, std::move(nm), a, b), model(std::move(mdl)) {
if (!iequals(model, "D")) {
throw runtime_error("Error: Unsupported diode model (“" + model + "”) in " + name + ".");
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> &vsMap) const override {
int i = nPlus, j = nMinus;
text// look up the extra row for this diode’s 0.7 V source auto it = vsMap.find(name); int vsIdx = it->second; int rowVS = nNode + vsIdx; double g = 1.0 / SMALL_RESISTANCE_R;
// —— stamp R_s between rowVS and node i (nPlus) ——
if (i != 0) {
A[rowVS][rowVS] += g;
A[i - 1][i - 1] += g;
A[rowVS][i - 1] -= g;
A[i - 1][rowVS] -= g;
}
// —— stamp R_s between rowVS and node j (nMinus) ——
if (j != 0) {
A[rowVS][rowVS] += g;
A[j - 1][j - 1] += g;
A[rowVS][j - 1] -= g;
A[j - 1][rowVS] -= g;
}
// —— now enforce the 0.7 V drop as before ——
if (i != 0) A[rowVS][i - 1] += +1.0;
if (j != 0) A[rowVS][j - 1] += -1.0;
b[rowVS] = DEFAULT_DIODE_DROP;
if (i != 0) A[i - 1][rowVS] += +1.0;
if (j != 0) A[j - 1][rowVS] += -1.0;
}
};
class VoltageSource : public Element {
public:
double Vdc;
VoltageSource(string nm, int a, int b, double val)
: Element(Element::VS, std::move(nm), a, b), Vdc(val) {
if (!(name.size()>0 && (name[0]=='V' || name[0]=='v'))) {
throw runtime_error("Error: Voltage source name must start with 'V': " + name);
}
}
void stamp(vector<vector<double>> &A,vector<double> &b,int nNode , const unordered_map<string,int> &vsMap) const override {
auto it = vsMap.find(name);
if (it == vsMap.end()) {
throw runtime_error("Error: VS index not found for " + name + ".");
}
int vsIndex = it->second;
int rowVS = nNode + vsIndex;
int i = nPlus, j = nMinus;
if (i != 0) A[rowVS][i-1] = +1.0;
if (j != 0) A[rowVS][j-1] = -1.0;
b[rowVS] = Vdc;
if (i != 0) A[i-1][rowVS] += +1.0;
if (j != 0) A[j-1][rowVS] += -1.0;
}
};
class CurrentSource : public Element {
public:
double I;
CurrentSource(string nm, int a, int b, double val)
: Element(Element::IS, std::move(nm), a, b), I(val) {
if (!(name.size()>0 && (name[0]=='I' || name[0]=='i'))) {
throw runtime_error("Error: Current source name must start with 'I': " + name);
}
}
void stamp(vector<vector<double>> & /A/,
vector<double> &b,
int /nNode/,
const unordered_map<string,int> & /vsMap/) const override {
int i = nPlus, j = nMinus;
if (i != 0) b[i-1] += -I;
if (j != 0) b[j-1] += +I;
}
};
class VCVS : public Element {
public:
int ctrlP, ctrlM;
double gain;
VCVS(string nm, int a, int b, int cp, int cm, double gv): Element(Element::VCVS, std::move(nm), a, b), ctrlP(cp), ctrlM(cm), gain(gv) {
if (!(name.size()>0 && (name[0]=='E' || name[0]=='e'))) {
throw runtime_error("Error: VCVS name must start with 'E': " + name);
}
}
void stamp(vector<vector<double>> &A,vector<double>&b,int nNode,const unordered_map<string,int> &vsMap) const override {
auto it = vsMap.find(name);
if (it == vsMap.end()) {
throw runtime_error("Error: VCVS index not found for " + name + ".");
}
int vsIndex = it->second;
int rowVS = nNode + vsIndex;
int i = nPlus, j = nMinus;
int cp = ctrlP, cm = ctrlM;
if (i != 0) A[rowVS][i-1] = +1.0;
if (j != 0) A[rowVS][j-1] = -1.0;
if (cp != 0) A[rowVS][cp-1] += -gain;
if (cm != 0) A[rowVS][cm-1] += +gain;
b[rowVS] = 0.0;
if (i != 0) A[i-1][rowVS] += +1.0;
if (j != 0) A[j-1][rowVS] += -1.0;
}
};
class CCCS : public Element {
public:
string ctrlVSname;
double gain;
CCCS(string nm, int a, int b, string cvs, double gv)
: Element(Element::CCCS, std::move(nm), a, b), ctrlVSname(std::move(cvs)), gain(gv) {
if (!(name.size()>0 && (name[0]=='F' || name[0]=='f'))) {
throw runtime_error("Error: CCCS name must start with 'F': " + name);
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> &vsMap) const override {
auto it = vsMap.find(ctrlVSname);
if (it == vsMap.end()) {
throw runtime_error("Error: controlling VS '" + ctrlVSname + "' not found for " + name + ".");
}
int ctrlIdx = it->second;
int colCtrl = nNode + ctrlIdx;
int i = nPlus, j = nMinus;
if (i != 0) A[i-1][colCtrl] += -gain;
if (j != 0) A[j-1][colCtrl] += +gain;
}
};
class CCVS : public Element {
public:
string ctrlVSname;
double gain;
CCVS(string nm, int a, int b, string cvs, double gv)
: Element(Element::CCVS, std::move(nm), a, b), ctrlVSname(std::move(cvs)), gain(gv) {
if (!(name.size()>0 && (name[0]=='H' || name[0]=='h'))) {
throw runtime_error("Error: CCVS name must start with 'H': " + name);
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> &vsMap) const override {
auto itSelf = vsMap.find(name);
if (itSelf == vsMap.end()) {
throw runtime_error("Error: CCVS index not found for " + name + ".");
}
int vsIndex = itSelf->second;
int rowVS = nNode + vsIndex;
auto itCtrl = vsMap.find(ctrlVSname);
if (itCtrl == vsMap.end()) {
throw runtime_error("Error: controlling VS '" + ctrlVSname + "' not found for " + name + ".");
}
int ctrlIdx = itCtrl->second;
int colCtrl = nNode + ctrlIdx;
int i = nPlus, j = nMinus;
if (i != 0) A[rowVS][i-1] = +1.0;
if (j != 0) A[rowVS][j-1] = -1.0;
A[rowVS][colCtrl] = -gain;
b[rowVS] = 0.0;
if (i != 0) A[i-1][rowVS] += +1.0;
if (j != 0) A[j-1][rowVS] += -1.0;
}
};
class VCCS : public Element {
public:
int ctrlP, ctrlM;
double gain;
VCCS(string nm, int a, int b, int cp, int cm, double gv)
: Element(Element::VCCS, std::move(nm), a, b), ctrlP(cp), ctrlM(cm), gain(gv) {
if (!(name.size()>0 && (name[0]=='G' || name[0]=='g'))) {
throw runtime_error("Error: VCCS name must start with 'G': " + name);
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> & /vsMap/) const override {
int i = nPlus, j = nMinus;
int cp = ctrlP, cm = ctrlM;
if (i != 0 && cp != 0) A[i-1][cp-1] += +gain;
if (i != 0 && cm != 0) A[i-1][cm-1] += -gain;
if (j != 0 && cp != 0) A[j-1][cp-1] += -gain;
if (j != 0 && cm != 0) A[j-1][cm-1] += +gain;
}
};
/**************************************************************************************************
PRINT REQUEST STRUCTURE (Section 15)
**************************************************************************************************/
struct PrintRequest {
enum Mode { DC, TRAN } mode;
string sourceName; // نام منبع ولتاژ برای DC sweep
double startVal, endVal, step;
vector<string> variables; // لیستی از عبارتهای V(<node>) یا I(<element>)
};
/**************************************************************************************************
CIRCUIT CLASS: Holds elements & parsing
************************************************************************************************/
class Circuit {
public:
NodeMap nodes;
vector< unique_ptr<Element> > elements;
unordered_map<string,int> vsMap;
bool readingDone = false;
vector<PrintRequest> printRequests;
/
* parseLine:
* پارس یک خط از نتلیست و اعمال دستورهای متنی.
* پشتیبانی از:
* add R,C,L,D,V,I,E,F,H,G
* delete <elementName>
* .nodes
* .list <type>
* rename <oldNode> <newNode>
* .print DC ...
* .end
/
/ saved state for C & L (keys = element name) */
std::unordered_mapstd::string,double capVPrev;
std::unordered_mapstd::string,double indIPrev;
void parseLine(const string &line) {
if (line.empty()) return;
vector<string> tok = tokenize(line);
if (tok.empty()) return;
string cmd = tok[0];
text// ===== دستور add ===== if (istarts_with(cmd, "add")) { if (tok.size() < 2) { throw runtime_error("Syntax error: 'add' needs arguments."); } string elemName = tok[1]; char c0 = toupper(elemName[0]); // اگر نوع V باشد و عبارت SIN در خط موجود باشد، آفست را استخراج کرده و // tok را به معادل DC بازسازی میکنیم: double sinOffset = nan(""); if (c0 == 'V') { sinOffset = parseSINOffset(line); if (!isnan(sinOffset)) { // بازسازی tok به صورت: add elemName n+ n- DC <sinOffset> if (tok.size() < 4) { throw runtime_error("Syntax error in SIN voltage source: " + line); } tok = { "add", elemName, tok[2], tok[3], "DC", to_string(sinOffset) }; } } if (c0 == 'R') { // فرمت: add R<name> n+ n- <value> if (tok.size() != 5) { throw runtime_error("Syntax error in resistor definition: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[4]); elements.emplace_back(new Resistor(elemName, n1, n2, val)); } else if (c0 == 'C') { // فرمت: add C<name> n+ n- <value> if (tok.size() != 5) { throw runtime_error("Syntax error in capacitor definition: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[4]); elements.emplace_back(new Capacitor(elemName, n1, n2, val)); } else if (c0 == 'L') { // فرمت: add L<name> n+ n- <value> if (tok.size() != 5) { throw runtime_error("Syntax error in inductor definition: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[4]); elements.emplace_back(new Inductor(elemName, n1, n2, val)); } else if (c0 == 'D') { // فرمت: add D<name> n+ n- <model> if (tok.size() < 4 || tok.size() > 5) { throw runtime_error("Syntax error in diode definition: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); string mdl = (tok.size() == 5 ? tok[4] : "D"); elements.emplace_back(new Diode(elemName, n1, n2, mdl)); } else if (c0 == 'V') { // حالا tok به صورت: [ "add", elemName, n+, n-, "DC", val ] است if (tok.size() != 6 || !iequals(tok[4], "DC")) { throw runtime_error("Syntax error in voltage source: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[5]); elements.emplace_back(new VoltageSource(elemName, n1, n2, val)); } else if (c0 == 'I') { // فرمت: add I<name> n+ n- DC <value> if (tok.size() != 6 || !iequals(tok[4], "DC")) { throw runtime_error("Syntax error in current source: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[5]); elements.emplace_back(new CurrentSource(elemName, n1, n2, val)); } else if (c0 == 'E') { // فرمت: add E<name> n+ n- c+ c- <gain> if (tok.size() != 7) { throw runtime_error("Syntax error in VCVS: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); int cp = nodes.getIdx(tok[4]); int cm = nodes.getIdx(tok[5]); double g = toDouble(tok[6]); elements.emplace_back(new VCVS(elemName, n1, n2, cp, cm, g)); } else if (c0 == 'F') { // فرمت: add F<name> n+ n- <ctrlVS> <gain> if (tok.size() != 6) { throw runtime_error("Syntax error in CCCS: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); string cvs = tok[4]; double g = toDouble(tok[5]); elements.emplace_back(new CCCS(elemName, n1, n2, cvs, g)); } else if (c0 == 'H') { // فرمت: add H<name> n+ n- <ctrlVS> <gain> if (tok.size() != 6) { throw runtime_error("Syntax error in CCVS: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); string cvs = tok[4]; double g = toDouble(tok[5]); elements.emplace_back(new CCVS(elemName, n1, n2, cvs, g)); } else if (c0 == 'G') { // فرمت: add G<name> n+ n- c+ c- <gain> if (tok.size() != 7) { throw runtime_error("Syntax error in VCCS: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); int cp = nodes.getIdx(tok[4]); int cm = nodes.getIdx(tok[5]); double g = toDouble(tok[6]); elements.emplace_back(new VCCS(elemName, n1, n2, cp, cm, g)); } else { throw runtime_error("Unknown element type in 'add': " + elemName); } } // ===== دستور delete ===== else if (istarts_with(cmd, "delete")) { // فرمت: delete <elementName> if (tok.size() != 2) { throw runtime_error("Syntax error: 'delete' requires exactly one argument."); } string enm = tok[1]; bool found = false; for (auto it = elements.begin(); it != elements.end(); ++it) { if (iequals((*it)->name, enm)) { elements.erase(it); found = true; break; } } if (!found) { throw runtime_error("Error: element '" + enm + "' not found."); } } // ===== دستور .nodes ===== else if (iequals(cmd, ".nodes")) { printNodes(); } // ===== دستور .list ===== else if (iequals(cmd, ".list")) { if (tok.size() != 2) { throw runtime_error("Syntax error: '.list' requires exactly one type argument."); } printList(tok[1]); } // ===== دستور rename ===== else if (iequals(cmd, "rename")) { if (tok.size() != 3) { throw runtime_error("Syntax error: 'rename' requires two arguments."); } nodes.renameNode(tok[1], tok[2]); } // ===== دستور .print ===== (بخش 15) else if (iequals(cmd, ".print")) { if (tok.size() < 7) { throw runtime_error("Syntax error in .print command: " + line); } PrintRequest pr; if (iequals(tok[1], "DC")) { pr.mode = PrintRequest::DC; pr.sourceName = tok[2]; pr.startVal = toDouble(tok[3]); pr.endVal = toDouble(tok[4]); pr.step = toDouble(tok[5]); // باقی توکنها متغیرهایی مثل V(node) یا I(element) for (size_t i = 6; i < tok.size(); i++) { pr.variables.push_back(tok[i]); } } else if (iequals(tok[1], "TRAN")) { pr.mode = PrintRequest::TRAN; pr.startVal = toDouble(tok[2]); // tstep pr.endVal = toDouble(tok[3]); // tstop pr.step = toDouble(tok[2]); // store tstep again double tstart = toDouble(tok[4]); // often 0 double tmax = toDouble(tok[5]); // ignored for now // vars begin at tok[6] for (size_t i=6;i<tok.size();++i) pr.variables.push_back(tok[i]); pr.sourceName = ""; // not used in TRAN } else { throw runtime_error("Only DC .print is supported in this version: " + line); } printRequests.push_back(move(pr)); } // ===== دستور .end ===== else if (iequals(cmd, ".end")) { readingDone = true; } else { throw runtime_error("Unknown command: " + line); } } /** * solveDC: * پس از دریافت دستور .end، ماتریس MNA را میسازد، حل میکند و نتایج را چاپ میکند. * اگر دستورات .print وجود داشته باشد، به جای یکبار حل، برای هر PrintRequest حل مکرر اجرا میشود. */ void solveDC() { // 1) ساخت vsMap: نگاشت نام منابع ولتاژ (شامل دیود و منابع وابسته) به یک ایندکس vsMap.clear(); int M = 0; for (const auto &e: elements) { if (e->type == Element::VS || e->type == Element::DIO || e->type == Element::VCVS || e->type == Element::CCVS) { vsMap[e->name] = M++; } } int totalNodes = nodes.count(); // شامل گره 0 int nNode = totalNodes - 1; // تعداد گرههای واقعی (بدون گره زمین) int N = nNode + M; // ابعاد ماتریس A و بردار b // اگر هیچ دستور .print تعریف نشده، یک بار حل کن و چاپ کن if (printRequests.empty()) { vector<vector<double>> A(N, vector<double>(N, 0.0)); vector<double> b(N, 0.0); for (const auto &e: elements) { e->stamp(A, b, nNode, vsMap); } vector<double> x = gaussianEliminate(A, b); cout << fixed << setprecision(6); cout << "\n=== DC Operating‐Point ===\n"; for (int i = 1; i < totalNodes; i++) { cout << "Node " << nodes.idx2name[i] << " (idx " << i << "): " << x[i - 1] << " V\n"; } cout << "Node 0 (GND): 0.000000 V\n"; for (const auto &p: vsMap) { const string &nm = p.first; int idx = p.second; double Iv = x[nNode + idx]; cout << "I(" << nm << "): " << Iv << " A\n"; } cout << "=== End of Results ===\n"; return; } // 2) برای هر دستور .print DC، یک loop از startVal تا endVal اجرا کن (شامل هر دو) با گام step else { for (const auto &pr: printRequests) if (pr.mode == PrintRequest::TRAN) simulateTRAN(pr.step, pr.endVal, pr.startVal, pr.variables); else if (pr.mode == PrintRequest::DC) { // چک کن که منبع ولتاژ وجود دارد const string &src = pr.sourceName; bool foundVS = false; VoltageSource *vsPtr = nullptr; for (auto &e: elements) { if (iequals(e->name, src) && e->type == Element::VS) { vsPtr = static_cast<VoltageSource *>(e.get()); foundVS = true; break; } } if (!foundVS) { cout << "ERROR: Voltage source '" << src << "' not found in circuit\n"; continue; } // چاپ عنوان جدول cout << "\n=== DC Sweep: Source " << src << " from " << pr.startVal << " to " << pr.endVal << " step " << pr.step << " ===\n"; // چاپ هدر: نام منبع، سپس هر متغیر cout << src; for (const auto &var: pr.variables) { cout << "\t" << var; } cout << "\n"; // حلقهٔ Sweep for (double Vsrc = pr.startVal; Vsrc <= pr.endVal + EPS; Vsrc += pr.step) { // تنظیم مقدار منبع ولتاژ روی Vsrc vsPtr->Vdc = Vsrc; // ساختن ماتریس و بردار برای هر تکرار vector<vector<double>> A(N, vector<double>(N, 0.0)); vector<double> b(N, 0.0); for (const auto &e: elements) { e->stamp(A, b, nNode, vsMap); } vector<double> x = gaussianEliminate(A, b); // چاپ خط: مقدار منبع، سپس مقادیر متغیرهای خواستهشده cout << fixed << setprecision(6) << Vsrc; for (const auto &var: pr.variables) { // متغیرهای ولتاژ: V(node) if (istarts_with(var, "V(") && var.back() == ')') { string nodeName = var.substr(2, var.size() - 3); if (!nodes.hasName(nodeName)) { cout << "\t" << "ERROR: Node " << nodeName << " not found"; } else { int idx = nodes.name2idx.at(nodeName); double Vnode = (idx == 0 ? 0.0 : x[idx - 1]); cout << "\t" << Vnode; } } // متغیرهای جریان: I(element) else if (istarts_with(var, "I(") && var.back() == ')') { string elemName = var.substr(2, var.size() - 3); bool printed = false; // 1) اگر المان در vsMap (منابع ولتاژ/دیود/VCVS/CCVS) بود: if (vsMap.count(elemName)) { int vsIdx = vsMap.at(elemName); double Ielem = x[nNode + vsIdx]; cout << "\t" << Ielem; printed = true; } else { // 2) جستوجو در لیست المانها برای مقاومت یا منبع جریان for (const auto &e: elements) { if (iequals(e->name, elemName)) { if (e->type == Element::RES) { // جریان مقاومت = (V(nPlus) - V(nMinus)) / R Resistor *rptr = static_cast<Resistor *>(e.get()); int np = rptr->nPlus, nm = rptr->nMinus; double Vp = (np == 0 ? 0.0 : x[np - 1]); double Vm = (nm == 0 ? 0.0 : x[nm - 1]); double Ires = (Vp - Vm) / rptr->R; cout << "\t" << Ires; printed = true; } else if (e->type == Element::IS) { // جریان منبع جریان مستقل: همان مقدار I CurrentSource *iptr = static_cast<CurrentSource *>(e.get()); cout << "\t" << iptr->I; printed = true; } // برای سایر انواع (مثلاً خازن/سلف) در DC منطق ساده نمیتوان جریان را تعریف کرد break; } } } if (!printed) { cout << "\t" << "ERROR: Element " << elemName << " not found"; } } else { cout << "\t" << "ERROR: Invalid variable '" << var << "'"; } } cout << "\n"; } cout << "=== End of DC Sweep for " << src << " ===\n"; } } } void simulateTRAN(double tstep, double tstop, double tstart, const std::vector<std::string>& vars) { // --- prepare constant info --- vsMap.clear(); int M = 0; for (const auto& e : elements) if (e->type == Element::VS || e->type == Element::DIO || e->type == Element::VCVS|| e->type == Element::CCVS) vsMap[e->name] = M++; int totalNodes = nodes.count(); int nNode = totalNodes - 1; int N = nNode + M; /* initialise past state to zero */ capVPrev.clear(); indIPrev.clear(); for (const auto& e : elements) if (e->type == Element::CAP) capVPrev[e->name] = 0.0; else if (e->type == Element::IND) indIPrev[e->name] = 0.0; std::cout << "Time"; for (const auto& v : vars) std::cout << '\t' << v; std::cout << '\n' << std::fixed << std::setprecision(6); // --- main time-loop --- for (double t = tstart; t <= tstop + EPS; t += tstep) { std::vector<std::vector<double>> A(N, std::vector<double>(N, 0.0)); std::vector<double> b(N, 0.0); // stamp every element (DC + transient part) for (const auto& e : elements) { e->stamp(A, b, nNode, vsMap); // DC part e->stampTransient(A, b, tstep, capVPrev, indIPrev, nNode, vsMap); // dynamic part } std::vector<double> x = gaussianEliminate(A, b); // ---- print requested variables ---- std::cout << t; for (const auto& var : vars) { if (startsWith(var, "V(") && var.back()==')') { // node voltage std::string node = extractNode(var); double v = (node=="0") ? 0.0 : x[nodes.name2idx[node]-1]; std::cout << '\t' << v; } else if (startsWith(var, "I(") && var.back()==')') { // element current std::string el = extractElement(var); double I = 0.0; if (vsMap.count(el)) // any device with extra MNA row I = x[nNode + vsMap[el]]; else { /* quick support for R, C, L, I */ for (const auto& e : elements) if (iequals(e->name, el)) { if (e->type == Element::RES) { auto* r = static_cast<Resistor*>(e.get()); double vp = (r->nPlus==0?0.0:x[r->nPlus-1]); double vn = (r->nMinus==0?0.0:x[r->nMinus-1]); I = (vp - vn)/r->R; } else if (e->type == Element::CAP){ auto* c = static_cast<Capacitor*>(e.get()); I = c->C / tstep * capVPrev[el]; }// approximate else if (e->type == Element::IND) I = indIPrev[el]; else if (e->type == Element::IS) I = static_cast<CurrentSource*>(e.get())->I; break; } } std::cout << '\t' << I; } else std::cout << "\tERR"; } std::cout << '\n'; for (const auto& e : elements) { if (e->type == Element::CAP) { auto* c = static_cast<Capacitor*>(e.get()); double vp = (c->nPlus==0?0.0:x[c->nPlus-1]); double vn = (c->nMinus==0?0.0:x[c->nMinus-1]); capVPrev[c->name] = vp - vn; // V[n+1] } else if (e->type == Element::IND) { auto* l = static_cast<Inductor*>(e.get()); double vp = (l->nPlus==0?0.0:x[l->nPlus-1]); double vn = (l->nMinus==0?0.0:x[l->nMinus-1]); double vL = vp - vn; indIPrev[l->name] += (tstep / l->L) * vL; // I[n+1] = I[n] + (dt/L)*v } } } }
public:
/**
* printNodes:
* چاپ لیست گرهها و اندیسهایشان.
*/
bool hasGroundNode() const {
return nodes.hasName("0");
}
bool isConnectedCircuit() const {
int n = nodes.count();
if (n <= 1) return true;
vector<vector<int>> adj(n);
for (const auto& e : elements) {
adj[e->nPlus].push_back(e->nMinus);
adj[e->nMinus].push_back(e->nPlus);
}
vector<bool> visited(n, false);
queue<int> q;
q.push(0);
visited[0] = true;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : adj[u]) {
if (!visited[v]) {
visited[v] = true;
q.push(v);
}
}
}
return all_of(visited.begin(), visited.end(), [](bool v) { return v; });
}
bool hasDuplicateNames() const {
unordered_set<string> names;
for (const auto& e : elements) {
string up = e->name;
transform(up.begin(), up.end(), up.begin(), ::toupper);
if (!names.insert(up).second) return true;
}
return false;
}
void printNodes() const {
cout << "\n-- Node List --\n";
for (int i = 0; i < nodes.count(); i++) {
cout << "Index " << i << " : " << nodes.idx2name[i] << "\n";
}
cout << "-- End Node List --\n";
}
void printList(const string &typ) const {
char t0 = toupper(typ[0]);
cout << "\n-- Elements of type " << typ << " --\n";
for (const auto &e : elements) {
bool match = false;
switch (t0) {
case 'R': match = (e->type == Element::RES); break;
case 'C': match = (e->type == Element::CAP); break;
case 'L': match = (e->type == Element::IND); break;
case 'D': match = (e->type == Element::DIO); break;
case 'V': match = (e->type == Element::VS); break;
case 'I': match = (e->type == Element::IS); break;
case 'E': match = (e->type == Element::VCVS); break;
case 'F': match = (e->type == Element::CCCS); break;
case 'H': match = (e->type == Element::CCVS); break;
case 'G': match = (e->type == Element::VCCS); break;
default:
throw runtime_error("Unknown element type for '.list': " + typ);
}
if (match) {
cout << e->name << " : ";
cout << "nodes[" << nodes.idx2name[e->nPlus] << ", "
<< nodes.idx2name[e->nMinus] << "]\n";
}
}
cout << "-- End List --\n";
}
text/** * gaussianEliminate: * حل دستگاه خطی A·x = b با روش حذف گاوس و قطبیابی جزئی. * اگر ماتریس تکین باشد، runtime_error پرتاب میکند. */ vector<double> gaussianEliminate(vector<vector<double>> &A, vector<double> &b) { int n = (int)b.size(); for (int k = 0; k < n; k++) { int piv = k; for (int i = k+1; i < n; i++) { if (fabs(A[i][k]) > fabs(A[piv][k])) piv = i; } if (fabs(A[piv][k]) < EPS) { throw runtime_error("Singular matrix detected during solve."); } if (piv != k) { swap(A[k], A[piv]); swap(b[k], b[piv]); } double diag = A[k][k]; for (int j = k; j < n; j++) A[k][j] /= diag; b[k] /= diag; for (int i = k+1; i < n; i++) { double factor = A[i][k]; if (fabs(factor) < EPS) continue; for (int j = k; j < n; j++) { A[i][j] -= factor * A[k][j]; } b[i] -= factor * b[k]; } } vector<double> x(n, 0.0); for (int i = n-1; i >= 0; i--) { double sum = b[i]; for (int j = i+1; j < n; j++) { sum -= A[i][j] * x[j]; } x[i] = sum; } return x; }
};
/**************************************************************************************************
MAIN
**************************************************************************************************/
int main(int argc, char *argv[]) {
try {
Circuit ckt;
textif (argc == 1) { string line; while (getline(cin, line)) { if (iequals(line, ".end")) break; ckt.parseLine(line); } } else { ifstream fin(argv[1]); if (!fin) throw runtime_error("Error: Input file not found."); string line; while (getline(fin, line)) { if (iequals(line, ".end")) break; ckt.parseLine(line); } } if (!ckt.hasGroundNode()) throw runtime_error("Error: No ground node detected in the circuit."); if (ckt.hasDuplicateNames()) throw runtime_error("Error: Duplicate node or component name detected."); if (!ckt.isConnectedCircuit()) throw runtime_error("Error: Circuit is not fully connected."); ckt.solveDC(); } catch (const exception &ex) { cerr << ex.what() << endl; return 1; } return 0;
}
Here’s a minimal netlist you can feed into your simulator to exercise a sinusoidal source over exactly one period (assuming you want a 50 Hz sine wave, period = 1/50 = 0.02 s):
spice* one–period 50 Hz sine test add V1 1 0 SIN(0 5 50 0) ; V1 node 1→0, SIN(offset=0 V, amp=5 V, freq=50 Hz, phase=0) add R1 1 0 1k ; 1 kΩ load from node 1 to ground .print TRAN 0.001 0.02 0 0 V(1) ; tstep=1 ms, tstop=20 ms, tstart=0, tmax(dummy)=0, measure V(1) .end
Save that as, say, test.cir
and run:
bash./your_simulator_exe < test.cir
You should see something like:
textTime V(1) 0.000000 0.000000 0.001000 3.09017 0.002000 4.75528 … 0.019000 0.312250 0.020000 0.000000
which traces out one full 5 V-peak sine wave over 20 ms.
but the output is like that
Time V(1)
0.001000 0.000000
0.002000 0.000000
0.003000 0.000000
0.004000 0.000000
0.005000 0.000000
0.006000 0.000000
0.007000 0.000000
0.008000 0.000000
0.009000 0.000000
0.010000 0.000000
0.011000 0.000000
0.012000 0.000000
0.013000 0.000000
0.014000 0.000000
0.015000 0.000000
0.016000 0.000000
0.017000 0.000000
0.018000 0.000000
0.019000 0.000000
0.020000 0.000000
What you’re seeing—all zeros—is exactly what your simulator does today: it takes your SIN(…)
line, parses out only the DC offset (which you gave as zero), and then treats every voltage source as a pure DC battery during the transient run. In other words, your code’s parseSINOffset
only grabs the first number inside SIN(…)
and then rebuilds the line as:
cppadd V1 1 0 DC 0.0
so in simulateTRAN
it never actually injects a time-varying waveform—just a constant 0 V.
You need to:
offset
, amplitude
, frequency
, phase
) and store them in your VoltageSource
object.stampTransient
for VoltageSource
so that at each time step t
you compute
and then stamp that into your MNA right-hand side (just like you do incppdouble vnow = offset + amplitude * sin(2 * M_PI * frequency * t + phase);
stamp
for DC).Extend the VoltageSource
class (add fields and a stampTransient
override):
cppclass VoltageSource : public Element { public: double Vdc; // legacy DC // new fields for SIN: bool isSin = false; double offset = 0.0; double amplitude = 0.0; double freq = 0.0; double phase = 0.0; // in radians VoltageSource(string nm,int a,int b,double val) : Element(Element::VS,move(nm),a,b), Vdc(val) { if (!(name.size()>0 && (name[0]=='V'||name[0]=='v'))) throw runtime_error("Error: Voltage source name must start with 'V': "+name); } // call this right after construction if you detect a SIN(...) line void setSin(double off,double amp,double f,double ph) { isSin = true; offset = off; amplitude = amp; freq = f; phase = ph; } void stamp(... same as before ...) const override { // unchanged: stamps DC‐part Vdc or offset ... } // *** NEW *** void stampTransient(vector<vector<double>>& A, vector<double>& b, double dt, unordered_map<string,double>& /*capVPrev*/, unordered_map<string,double>& /*indIPrev*/, int nNode, const unordered_map<string,int>& vsMap, double time) const override { // find our row int vsIdx = vsMap.at(name); int rowVS = nNode + vsIdx; double vnow = isSin ? offset + amplitude * sin(2.0*M_PI*freq*time + phase) : Vdc; // identical stamping to DC: set b[rowVS] and its KCL companions // (you could zero out A[rowVS] first if you like) // then: if (nPlus!=0) A[rowVS][nPlus-1] = +1.0, A[nPlus-1][rowVS] += +1.0; if (nMinus!=0)A[rowVS][nMinus-1]= -1.0, A[nMinus-1][rowVS]+= -1.0; b[rowVS] = vnow; } };
Update your parseLine
so that when you see SIN(off, amp, freq, phase)
you:
new VoltageSource(...)
with the DC‐placeholder offset
, then.setSin(offset, amplitude, frequency, phase)
on that object instead of throwing away the AMP/FREQ/PHASE.Modify your simulateTRAN
loop to pass the current time into stampTransient
. Change:
cppe->stamp(A, b, nNode, vsMap); e->stampTransient(A, b, tstep, capVPrev, indIPrev, nNode, vsMap);
into something like:
cppe->stamp(A, b, nNode, vsMap); e->stampTransient(A, b, tstep, capVPrev, indIPrev, nNode, vsMap, t);
(You’ll have to tweak the signature of stampTransient
accordingly.)
Once you do that, your original netlist
spiceadd V1 1 0 SIN(0 5 50 0) add R1 1 0 1k .print TRAN 0.001 0.02 0 0 V(1) .end
will actually inject a 5 V sine at 50 Hz, and you’ll see:
textTime V(1) 0.000000 0.000000 0.001000 3.090170 0.002000 4.755282 … 0.019000 0.312250 0.020000 0.000000
over one complete period.
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <algorithm>
#include <cctype>
#include <sstream>
#include <unordered_map>
#include <iomanip>
#include <cmath>
#include <memory>
#include <queue>
#include <unordered_set>
using namespace std;
/**************************************************************************************************
CONSTANTS
**************************************************************************************************/
static constexpr double DEFAULT_DIODE_DROP = 0.7; // ولتاژ افت دیود ایدهآل (۷۰۰ mV)
static constexpr double SMALL_RESISTANCE_R = 1e-3; // مقاومت خیلی کوچک (1 mΩ) برای دیود/سلف در DC
static constexpr double EPS = 1e-12; // برای تشخیص تکینبودن ماتریس
/**************************************************************************************************
UTILITY FUNCTIONS
**************************************************************************************************/
// tiny helpers – put in a common header or anonymous namespace
static bool startsWith(const string& s, const string& prefix) { return s.rfind(prefix,0)==0; }
static string extractNode(const string& var) { auto l=var.find('('); auto r=var.find(')'); return var.substr(l+1,r-l-1); }
static string extractElement(const string& var){ auto l=var.find('('); auto r=var.find(')'); return var.substr(l+1,r-l-1); }
static vector<string> tokenize(const string &line) {
vector<string> tokens;
istringstream iss(line);
string word;
while (iss >> word) {
tokens.push_back(word);
}
return tokens;
}
// تابع تبدیل رشته به حروف کوچک
string toLower(const string& s) {
string result = s;
transform(result.begin(), result.end(), result.begin(), ::tolower);
return result;
}
// تابع بررسی وجود فایل
bool fileExists(const string& filename) {
ifstream file(filename);
return file.good();
}
// تابع نمایش محتوای فایل شماتیک
void showSchematicContent(const string& filename) {
ifstream file(filename);
if (!file) {
cout << "Error: Could not open file " << filename << endl;
return;
}
textcout << filename << ":" << endl; string line; while (getline(file, line)) { cout << line << endl; } file.close();
}
// تابع اصلی منوی نمایش شماتیکها
void showExistingSchematicsMenu() {
vector<string> schematics = {
"draft1.txt",
"draft2.txt",
"draft3.txt",
"elecphase1.txt"
};
textwhile (true) { cout << "-choose existing schematic:" << endl; for (size_t i = 0; i < schematics.size(); i++) { cout << i+1 << "-" << schematics[i] << endl; } string input; cout << ">>> "; getline(cin, input); // خروج از منو اگر کاربر return وارد کند if (toLower(input) == "return") { break; } // بررسی ورودی جدید NewFile if (input.size() > 8 && toLower(input.substr(0, 8)) == "newfile ") { string filePath = input.substr(8); if (fileExists(filePath)) { showSchematicContent(filePath); } else { cout << "Error: File not found" << endl; } continue; } // بررسی ورودی عددی try { int choice = stoi(input); if (choice >= 1 && choice <= static_cast<int>(schematics.size())) { showSchematicContent(schematics[choice-1]); } else { cout << "-Error : Inappropriate input" << endl; } } catch (...) { cout << "-Error : Inappropriate input" << endl; } }
}
/**
/**
/**
double multiplier = 1.0;
textif (str.size() >= 3 && str.substr(str.size() - 3) == "meg") { multiplier = 1e6; str = str.substr(0, str.size() - 3); } else if (!str.empty() && str.back() == 'g') { multiplier = 1e9; str.pop_back(); } else if (!str.empty() && str.back() == 'k') { multiplier = 1e3; str.pop_back(); } else if (!str.empty() && str.back() == 'm') { multiplier = 1e-3; str.pop_back(); } else if (!str.empty() && str.back() == 'u') { multiplier = 1e-6; str.pop_back(); } else if (!str.empty() && str.back() == 'n') { multiplier = 1e-9; str.pop_back(); } else if (!str.empty() && str.back() == 'p') { multiplier = 1e-12; str.pop_back(); } else if (!str.empty() && str.back() == 'f') { multiplier = 1e-15; str.pop_back(); } try { size_t idx; double base = stod(str, &idx); if (idx != str.size()) throw runtime_error(""); return base * multiplier; } catch (...) { throw runtime_error("Invalid numeric value: " + s); }
}
/**
/**************************************************************************************************
NODE MAPPING (STRING → INTEGER)
**************************************************************************************************/
struct NodeMap {
unordered_map<string,int> name2idx;
vector<string> idx2name;
textNodeMap() { // گره زمین با نام "0" معادل اندیس 0 name2idx["0"] = 0; idx2name.push_back("0"); } /** * getIdx: * اگر نام گره ثبتشده باشد اندیس آن را برمیگرداند، در غیر این صورت یک اندیس جدید اختصاص میدهد. */ int getIdx(const string &nodeName) { if (name2idx.count(nodeName)) return name2idx[nodeName]; int newIdx = (int)idx2name.size(); name2idx[nodeName] = newIdx; idx2name.push_back(nodeName); return newIdx; } bool hasName(const string &nodeName) const { return name2idx.count(nodeName); } int count() const { return (int)idx2name.size(); } /** * renameNode: * نام گره oldName را به newName تغییر میدهد. اگر oldName وجود نداشته باشد یا newName * تکراری باشد، خطا پرتاب میکند. */ void renameNode(const string &oldName, const string &newName) { if (!name2idx.count(oldName)) { throw runtime_error("Error: node '" + oldName + "' not found."); } if (name2idx.count(newName)) { throw runtime_error("Error: node name '" + newName + "' already exists."); } int idx = name2idx[oldName]; name2idx.erase(oldName); name2idx[newName] = idx; idx2name[idx] = newName; }
};
/**************************************************************************************************
ELEMENT BASE CLASS
**************************************************************************************************/
class Element {
public:
enum Type { RES, CAP, IND, DIO, VS, IS, VCVS, CCCS, CCVS, VCCS };
Element(Type t, string nm, int n1, int n2)
: type(t), name(std::move(nm)), nPlus(n1), nMinus(n2) {}
virtual ~Element() = default;
textType type; // نوع المان string name; // نام المان int nPlus; // اندیس گره مثبت یا اول int nMinus; // اندیس گره منفی یا دوم /** * stamp: * وظیفه دارد مهرگذاری المان در ماتریس A و بردار b را انجام دهد. * nNode تعداد گرههای غیر از زمین است و vsMap نگاشت نام منابع ولتاژ به اندیس آنهاست. */ virtual void stamp(vector<vector<double>> &A, vector<double> &b,int nNode, const unordered_map<string,int> &vsMap) const = 0; virtual void stampTransient(std::vector<std::vector<double>>& /*A*/, std::vector<double>& /*b*/, double /*dt*/, std::unordered_map<std::string,double>& /*capVPrev*/, std::unordered_map<std::string,double>& /*indIPrev*/, int /*nNode*/, const std::unordered_map<std::string,int>& /*vsMap*/) const { /* empty – override in C & L */ }
};
/**************************************************************************************************
CONCRETE ELEMENT CLASSES
**************************************************************************************************/
class Resistor : public Element {
public:
double R;
Resistor(string nm, int a, int b, double val)
: Element(Element::RES, std::move(nm), a, b), R(val) {
if (R <= 0.0) {
throw runtime_error("Error: Resistance must be > 0 (" + name + ").");
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> & /vsMap/) const override {
double g = 1.0 / R;
int i = nPlus, j = nMinus;
if (i != 0) A[i-1][i-1] += g;
if (j != 0) A[j-1][j-1] += g;
if (i != 0 && j != 0) {
A[i-1][j-1] -= g;
A[j-1][i-1] -= g;
}
}
};
class Capacitor : public Element {
public:
double C;
Capacitor(string nm, int a, int b, double val)
: Element(Element::CAP, std::move(nm), a, b), C(val) {
if (C < 0.0) {
throw runtime_error("Error: Capacitance must be ≥ 0 (" + name + ").");
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> & /vsMap/) const override {
// در DC مثل مدار باز → هیچ مهرگذاری انجام نمیشود.
}
void stampTransient(std::vector<std::vector<double>>& A,
std::vector<double>& b,
double dt,
std::unordered_mapstd::string,double& capVPrev,
std::unordered_mapstd::string,double& /indIPrev/,
int nNode,
const std::unordered_mapstd::string,int& /vsMap/) const override
{
double G = C / dt; // Norton conductance
double Vpr = capVPrev[name]; // V[n]
double Ieq = G * Vpr; // companion current source
textint i = nPlus, j = nMinus; if (i != 0) A[i-1][i-1] += G; if (j != 0) A[j-1][j-1] += G; if (i != 0 && j != 0) { A[i-1][j-1] -= G; A[j-1][i-1] -= G; } if (i != 0) b[i-1] += Ieq; if (j != 0) b[j-1] += -Ieq; }
};
class Inductor : public Element {
public:
double L;
Inductor(string nm, int a, int b, double val)
: Element(Element::IND, std::move(nm), a, b), L(val) {
if (L < 0.0) {
throw runtime_error("Error: Inductance must be ≥ 0 (" + name + ").");
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> & /vsMap/) const override {
// مدلسازی سلف در DC به کمک مقاومت کوچک سری
double g = 1.0 / SMALL_RESISTANCE_R;
int i = nPlus, j = nMinus;
if (i != 0) A[i-1][i-1] += g;
if (j != 0) A[j-1][j-1] += g;
if (i != 0 && j != 0) {
A[i-1][j-1] -= g;
A[j-1][i-1] -= g;
}
}
void stampTransient(std::vector<std::vector<double>>& A,
std::vector<double>& b,
double dt,
std::unordered_mapstd::string,double& /capVPrev/,
std::unordered_mapstd::string,double& indIPrev,
int nNode,
const std::unordered_mapstd::string,int& /vsMap/) const override
{
double G = dt / L; // Norton conductance (G = 1/R)
double Ipr = indIPrev[name]; // I[n]
// Ieq is simply previous current (Norton current source)
double Ieq = Ipr;
textint i = nPlus, j = nMinus; if (i != 0) A[i-1][i-1] += G; if (j != 0) A[j-1][j-1] += G; if (i != 0 && j != 0) { A[i-1][j-1] -= G; A[j-1][i-1] -= G; } if (i != 0) b[i-1] += -Ieq; // sign is opposite of capacitor! if (j != 0) b[j-1] += Ieq; }
};
class Diode : public Element {
public:
string model;
Diode(string nm, int a, int b, string mdl)
: Element(Element::DIO, std::move(nm), a, b), model(std::move(mdl)) {
if (!iequals(model, "D")) {
throw runtime_error("Error: Unsupported diode model (“" + model + "”) in " + name + ".");
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> &vsMap) const override {
int i = nPlus, j = nMinus;
text// look up the extra row for this diode’s 0.7 V source auto it = vsMap.find(name); int vsIdx = it->second; int rowVS = nNode + vsIdx; double g = 1.0 / SMALL_RESISTANCE_R;
// —— stamp R_s between rowVS and node i (nPlus) ——
if (i != 0) {
A[rowVS][rowVS] += g;
A[i - 1][i - 1] += g;
A[rowVS][i - 1] -= g;
A[i - 1][rowVS] -= g;
}
// —— stamp R_s between rowVS and node j (nMinus) ——
if (j != 0) {
A[rowVS][rowVS] += g;
A[j - 1][j - 1] += g;
A[rowVS][j - 1] -= g;
A[j - 1][rowVS] -= g;
}
// —— now enforce the 0.7 V drop as before ——
if (i != 0) A[rowVS][i - 1] += +1.0;
if (j != 0) A[rowVS][j - 1] += -1.0;
b[rowVS] = DEFAULT_DIODE_DROP;
if (i != 0) A[i - 1][rowVS] += +1.0;
if (j != 0) A[j - 1][rowVS] += -1.0;
}
};
class VoltageSource : public Element {
public:
double Vdc;
VoltageSource(string nm, int a, int b, double val)
: Element(Element::VS, std::move(nm), a, b), Vdc(val) {
if (!(name.size()>0 && (name[0]=='V' || name[0]=='v'))) {
throw runtime_error("Error: Voltage source name must start with 'V': " + name);
}
}
void stamp(vector<vector<double>> &A,vector<double> &b,int nNode , const unordered_map<string,int> &vsMap) const override {
auto it = vsMap.find(name);
if (it == vsMap.end()) {
throw runtime_error("Error: VS index not found for " + name + ".");
}
int vsIndex = it->second;
int rowVS = nNode + vsIndex;
int i = nPlus, j = nMinus;
if (i != 0) A[rowVS][i-1] = +1.0;
if (j != 0) A[rowVS][j-1] = -1.0;
b[rowVS] = Vdc;
if (i != 0) A[i-1][rowVS] += +1.0;
if (j != 0) A[j-1][rowVS] += -1.0;
}
};
class CurrentSource : public Element {
public:
double I;
CurrentSource(string nm, int a, int b, double val)
: Element(Element::IS, std::move(nm), a, b), I(val) {
if (!(name.size()>0 && (name[0]=='I' || name[0]=='i'))) {
throw runtime_error("Error: Current source name must start with 'I': " + name);
}
}
void stamp(vector<vector<double>> & /A/,
vector<double> &b,
int /nNode/,
const unordered_map<string,int> & /vsMap/) const override {
int i = nPlus, j = nMinus;
if (i != 0) b[i-1] += -I;
if (j != 0) b[j-1] += +I;
}
};
class VCVS : public Element {
public:
int ctrlP, ctrlM;
double gain;
VCVS(string nm, int a, int b, int cp, int cm, double gv): Element(Element::VCVS, std::move(nm), a, b), ctrlP(cp), ctrlM(cm), gain(gv) {
if (!(name.size()>0 && (name[0]=='E' || name[0]=='e'))) {
throw runtime_error("Error: VCVS name must start with 'E': " + name);
}
}
void stamp(vector<vector<double>> &A,vector<double>&b,int nNode,const unordered_map<string,int> &vsMap) const override {
auto it = vsMap.find(name);
if (it == vsMap.end()) {
throw runtime_error("Error: VCVS index not found for " + name + ".");
}
int vsIndex = it->second;
int rowVS = nNode + vsIndex;
int i = nPlus, j = nMinus;
int cp = ctrlP, cm = ctrlM;
if (i != 0) A[rowVS][i-1] = +1.0;
if (j != 0) A[rowVS][j-1] = -1.0;
if (cp != 0) A[rowVS][cp-1] += -gain;
if (cm != 0) A[rowVS][cm-1] += +gain;
b[rowVS] = 0.0;
if (i != 0) A[i-1][rowVS] += +1.0;
if (j != 0) A[j-1][rowVS] += -1.0;
}
};
class CCCS : public Element {
public:
string ctrlVSname;
double gain;
CCCS(string nm, int a, int b, string cvs, double gv)
: Element(Element::CCCS, std::move(nm), a, b), ctrlVSname(std::move(cvs)), gain(gv) {
if (!(name.size()>0 && (name[0]=='F' || name[0]=='f'))) {
throw runtime_error("Error: CCCS name must start with 'F': " + name);
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> &vsMap) const override {
auto it = vsMap.find(ctrlVSname);
if (it == vsMap.end()) {
throw runtime_error("Error: controlling VS '" + ctrlVSname + "' not found for " + name + ".");
}
int ctrlIdx = it->second;
int colCtrl = nNode + ctrlIdx;
int i = nPlus, j = nMinus;
if (i != 0) A[i-1][colCtrl] += -gain;
if (j != 0) A[j-1][colCtrl] += +gain;
}
};
class CCVS : public Element {
public:
string ctrlVSname;
double gain;
CCVS(string nm, int a, int b, string cvs, double gv)
: Element(Element::CCVS, std::move(nm), a, b), ctrlVSname(std::move(cvs)), gain(gv) {
if (!(name.size()>0 && (name[0]=='H' || name[0]=='h'))) {
throw runtime_error("Error: CCVS name must start with 'H': " + name);
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> &vsMap) const override {
auto itSelf = vsMap.find(name);
if (itSelf == vsMap.end()) {
throw runtime_error("Error: CCVS index not found for " + name + ".");
}
int vsIndex = itSelf->second;
int rowVS = nNode + vsIndex;
auto itCtrl = vsMap.find(ctrlVSname);
if (itCtrl == vsMap.end()) {
throw runtime_error("Error: controlling VS '" + ctrlVSname + "' not found for " + name + ".");
}
int ctrlIdx = itCtrl->second;
int colCtrl = nNode + ctrlIdx;
int i = nPlus, j = nMinus;
if (i != 0) A[rowVS][i-1] = +1.0;
if (j != 0) A[rowVS][j-1] = -1.0;
A[rowVS][colCtrl] = -gain;
b[rowVS] = 0.0;
if (i != 0) A[i-1][rowVS] += +1.0;
if (j != 0) A[j-1][rowVS] += -1.0;
}
};
class VCCS : public Element {
public:
int ctrlP, ctrlM;
double gain;
VCCS(string nm, int a, int b, int cp, int cm, double gv)
: Element(Element::VCCS, std::move(nm), a, b), ctrlP(cp), ctrlM(cm), gain(gv) {
if (!(name.size()>0 && (name[0]=='G' || name[0]=='g'))) {
throw runtime_error("Error: VCCS name must start with 'G': " + name);
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> & /vsMap/) const override {
int i = nPlus, j = nMinus;
int cp = ctrlP, cm = ctrlM;
if (i != 0 && cp != 0) A[i-1][cp-1] += +gain;
if (i != 0 && cm != 0) A[i-1][cm-1] += -gain;
if (j != 0 && cp != 0) A[j-1][cp-1] += -gain;
if (j != 0 && cm != 0) A[j-1][cm-1] += +gain;
}
};
/**************************************************************************************************
PRINT REQUEST STRUCTURE (Section 15)
**************************************************************************************************/
struct PrintRequest {
enum Mode { DC, TRAN } mode;
string sourceName; // نام منبع ولتاژ برای DC sweep
double startVal, endVal, step;
vector<string> variables; // لیستی از عبارتهای V(<node>) یا I(<element>)
};
/**************************************************************************************************
CIRCUIT CLASS: Holds elements & parsing
************************************************************************************************/
class Circuit {
public:
NodeMap nodes;
vector< unique_ptr<Element> > elements;
unordered_map<string,int> vsMap;
bool readingDone = false;
vector<PrintRequest> printRequests;
/
* parseLine:
* پارس یک خط از نتلیست و اعمال دستورهای متنی.
* پشتیبانی از:
* add R,C,L,D,V,I,E,F,H,G
* delete <elementName>
* .nodes
* .list <type>
* rename <oldNode> <newNode>
* .print DC ...
* .end
/
/ saved state for C & L (keys = element name) */
std::unordered_mapstd::string,double capVPrev;
std::unordered_mapstd::string,double indIPrev;
void parseLine(const string &line) {
char first = toupper(line[0]);
if (first == 'R' || first == 'C' || first == 'L' || first == 'D'||
first == 'V' || first == 'I' || first == 'E' || first == 'F' ||
first == 'H' || first == 'G') {
parseLine("add " + line);
return;
}
if (line.empty()) return;
vector<string> tok = tokenize(line);
if (tok.empty()) return;
string cmd = tok[0];
text// ===== دستور add ===== if (istarts_with(cmd, "add")) { if (tok.size() < 2) { throw runtime_error("Syntax error: 'add' needs arguments."); } string elemName = tok[1]; char c0 = toupper(elemName[0]); // اگر نوع V باشد و عبارت SIN در خط موجود باشد، آفست را استخراج کرده و // tok را به معادل DC بازسازی میکنیم: double sinOffset = nan(""); if (c0 == 'V') { sinOffset = parseSINOffset(line); if (!isnan(sinOffset)) { // بازسازی tok به صورت: add elemName n+ n- DC <sinOffset> if (tok.size() < 4) { throw runtime_error("Syntax error in SIN voltage source: " + line); } tok = { "add", elemName, tok[2], tok[3], "DC", to_string(sinOffset) }; } } if (c0 == 'R') { // فرمت: add R<name> n+ n- <value> if (tok.size() != 5) { throw runtime_error("Syntax error in resistor definition: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[4]); elements.emplace_back(new Resistor(elemName, n1, n2, val)); } else if (c0 == 'C') { // فرمت: add C<name> n+ n- <value> if (tok.size() != 5) { throw runtime_error("Syntax error in capacitor definition: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[4]); elements.emplace_back(new Capacitor(elemName, n1, n2, val)); } else if (c0 == 'L') { // فرمت: add L<name> n+ n- <value> if (tok.size() != 5) { throw runtime_error("Syntax error in inductor definition: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[4]); elements.emplace_back(new Inductor(elemName, n1, n2, val)); } else if (c0 == 'D') { // فرمت: add D<name> n+ n- <model> if (tok.size() < 4 || tok.size() > 5) { throw runtime_error("Syntax error in diode definition: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); string mdl = (tok.size() == 5 ? tok[4] : "D"); elements.emplace_back(new Diode(elemName, n1, n2, mdl)); } else if (c0 == 'V') { // حالا tok به صورت: [ "add", elemName, n+, n-, "DC", val ] است if (tok.size() != 6 || !iequals(tok[4], "DC")) { throw runtime_error("Syntax error in voltage source: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[5]); elements.emplace_back(new VoltageSource(elemName, n1, n2, val)); } else if (c0 == 'I') { // فرمت: add I<name> n+ n- DC <value> if (tok.size() != 6 || !iequals(tok[4], "DC")) { throw runtime_error("Syntax error in current source: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[5]); elements.emplace_back(new CurrentSource(elemName, n1, n2, val)); } else if (c0 == 'E') { // فرمت: add E<name> n+ n- c+ c- <gain> if (tok.size() != 7) { throw runtime_error("Syntax error in VCVS: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); int cp = nodes.getIdx(tok[4]); int cm = nodes.getIdx(tok[5]); double g = toDouble(tok[6]); elements.emplace_back(new VCVS(elemName, n1, n2, cp, cm, g)); } else if (c0 == 'F') { // فرمت: add F<name> n+ n- <ctrlVS> <gain> if (tok.size() != 6) { throw runtime_error("Syntax error in CCCS: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); string cvs = tok[4]; double g = toDouble(tok[5]); elements.emplace_back(new CCCS(elemName, n1, n2, cvs, g)); } else if (c0 == 'H') { // فرمت: add H<name> n+ n- <ctrlVS> <gain> if (tok.size() != 6) { throw runtime_error("Syntax error in CCVS: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); string cvs = tok[4]; double g = toDouble(tok[5]); elements.emplace_back(new CCVS(elemName, n1, n2, cvs, g)); } else if (c0 == 'G') { // فرمت: add G<name> n+ n- c+ c- <gain> if (tok.size() != 7) { throw runtime_error("Syntax error in VCCS: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); int cp = nodes.getIdx(tok[4]); int cm = nodes.getIdx(tok[5]); double g = toDouble(tok[6]); elements.emplace_back(new VCCS(elemName, n1, n2, cp, cm, g)); } else { throw runtime_error("Unknown element type in 'add': " + elemName); } } // ===== دستور delete ===== else if (istarts_with(cmd, "delete")) { // فرمت: delete <elementName> if (tok.size() != 2) { throw runtime_error("Syntax error: 'delete' requires exactly one argument."); } string enm = tok[1]; bool found = false; for (auto it = elements.begin(); it != elements.end(); ++it) { if (iequals((*it)->name, enm)) { elements.erase(it); found = true; break; } } if (!found) { throw runtime_error("Error: element '" + enm + "' not found."); } } // ===== دستور .nodes ===== else if (iequals(cmd, ".nodes")) { printNodes(); } // ===== دستور .list ===== else if (iequals(cmd, ".list")) { if (tok.size() != 2) { throw runtime_error("Syntax error: '.list' requires exactly one type argument."); } printList(tok[1]); } // ===== دستور rename ===== else if (iequals(cmd, "rename")) { if (tok.size() != 3) { throw runtime_error("Syntax error: 'rename' requires two arguments."); } nodes.renameNode(tok[1], tok[2]); } // ===== دستور .print ===== (بخش 15) else if (iequals(cmd, ".print")) { if (tok.size() < 7) { throw runtime_error("Syntax error in .print command: " + line); } PrintRequest pr; if (iequals(tok[0], ".print") && iequals(tok[1], "DC") && tok.size() >= 3 && tok.size() < 7) { pr.mode = PrintRequest::DC; pr.sourceName = ""; // منبع sweep وجود ندارد pr.startVal = 0; pr.endVal = 0; pr.step = 0; for (size_t i = 2; i < tok.size(); ++i) { pr.variables.push_back(tok[i]); } printRequests.push_back(move(pr)); return; } if (iequals(tok[1], "DC")) { pr.mode = PrintRequest::DC; if (tok.size() >= 7) { pr.sourceName = tok[2]; pr.startVal = toDouble(tok[3]); pr.endVal = toDouble(tok[4]); pr.step = toDouble(tok[5]); for (int i = 6; i < tok.size(); i++) pr.variables.push_back(tok[i]); } else { // پشتیبانی از .print DC بدون sweep pr.sourceName = ""; pr.startVal = pr.endVal = pr.step = 0; for (int i = 2; i < tok.size(); i++) pr.variables.push_back(tok[i]); } } else if (iequals(tok[1], "TRAN")) { pr.mode = PrintRequest::TRAN; pr.startVal = toDouble(tok[2]); // tstep pr.endVal = toDouble(tok[3]); // tstop pr.step = toDouble(tok[2]); // store tstep again double tstart = toDouble(tok[4]); // often 0 double tmax = toDouble(tok[5]); // ignored for now // vars begin at tok[6] for (size_t i=6;i<tok.size();++i) pr.variables.push_back(tok[i]); pr.sourceName = ""; // not used in TRAN } else { throw runtime_error("Only DC .print is supported in this version: " + line); } printRequests.push_back(move(pr)); } // ===== دستور .end ===== else if (iequals(cmd, ".end")) { readingDone = true; } else { throw runtime_error("Unknown command: " + line); } } /** * solveDC: * پس از دریافت دستور .end، ماتریس MNA را میسازد، حل میکند و نتایج را چاپ میکند. * اگر دستورات .print وجود داشته باشد، به جای یکبار حل، برای هر PrintRequest حل مکرر اجرا میشود. */ void solveDC() { // 1) ساخت vsMap: نگاشت نام منابع ولتاژ (شامل دیود و منابع وابسته) به یک ایندکس vsMap.clear(); int M = 0; for (const auto &e: elements) { if (e->type == Element::VS || e->type == Element::DIO || e->type == Element::VCVS || e->type == Element::CCVS) { vsMap[e->name] = M++; } } int totalNodes = nodes.count(); // شامل گره 0 int nNode = totalNodes - 1; // تعداد گرههای واقعی (بدون گره زمین) int N = nNode + M; // ابعاد ماتریس A و بردار b // اگر هیچ دستور .print تعریف نشده، یک بار حل کن و چاپ کن if (printRequests.empty()) { bool hasSweep = false; for (const auto &pr: printRequests) if (pr.mode == PrintRequest::DC && !pr.sourceName.empty()) hasSweep = true; if (!hasSweep) { vector<vector<double>> A(N, vector<double>(N, 0.0)); vector<double> b(N, 0.0); for (const auto &e: elements) { e->stamp(A, b, nNode, vsMap); } vector<double> x = gaussianEliminate(A, b); for (const auto& pr : printRequests) { if (pr.mode != PrintRequest::DC || !pr.variables.size()) continue; cout << "\n--- Custom DC Output ---\n"; for (const auto &var: pr.variables) { if (istarts_with(var, "V(") && var.back() == ')') { string nodeName = var.substr(2, var.size() - 3); if (!nodes.hasName(nodeName)) { cout << "V(" << nodeName << "): ERROR - Node not found\n"; } else { int idx = nodes.name2idx.at(nodeName); double Vnode = (idx == 0 ? 0.0 : x[idx - 1]); cout << "V(" << nodeName << "): " << Vnode << " V\n"; } } else if (istarts_with(var, "I(") && var.back() == ')') { string elemName = var.substr(2, var.size() - 3); bool printed = false; if (vsMap.count(elemName)) { int vsIdx = vsMap.at(elemName); double Ielem = x[nNode + vsIdx]; cout << "I(" << elemName << "): " << Ielem << " A\n"; printed = true; } else { for (const auto& e : elements) { if (iequals(e->name, elemName)) { if (e->type == Element::RES) { auto* r = static_cast<Resistor*>(e.get()); int np = r->nPlus, nm = r->nMinus; double Vp = (np == 0 ? 0.0 : x[np - 1]); double Vm = (nm == 0 ? 0.0 : x[nm - 1]); double Ires = (Vp - Vm) / r->R; cout << "I(" << elemName << "): " << Ires << " A\n"; printed = true; } else if (e->type == Element::IS) { auto* isrc = static_cast<CurrentSource*>(e.get()); cout << "I(" << elemName << "): " << isrc->I << " A\n"; printed = true; } break; } } } if (!printed) { cout << "I(" << elemName << "): ERROR - Element not found\n"; } } else { cout << var << ": ERROR - Invalid variable\n"; } } cout << "--- End Custom DC Output ---\n"; } cout << fixed << setprecision(6); cout << "\n=== DC Operating-Point ===\n"; for (int i = 1; i < totalNodes; i++) { cout << "Node " << nodes.idx2name[i] << " (idx " << i << "): " << x[i - 1] << " V\n"; } cout << "Node 0 (GND): 0.000000 V\n"; for (const auto &p: vsMap) { const string &nm = p.first; int idx = p.second; double Iv = x[nNode + idx]; cout << "I(" << nm << "): " << Iv << " A\n"; } cout << "=== End of Results ===\n"; return; } // 2) برای هر دستور .print DC، یک loop از startVal تا endVal اجرا کن (شامل هر دو) با گام step else { for (const auto &pr: printRequests) if (pr.mode == PrintRequest::TRAN) simulateTRAN(pr.step, pr.endVal, pr.startVal, pr.variables); else if (pr.mode == PrintRequest::DC) { // چک کن که منبع ولتاژ وجود دارد const string &src = pr.sourceName; bool foundVS = false; VoltageSource *vsPtr = nullptr; for (auto &e: elements) { if (iequals(e->name, src) && e->type == Element::VS) { vsPtr = static_cast<VoltageSource *>(e.get()); foundVS = true; break; } } if (!foundVS) { cout << "ERROR: Voltage source '" << src << "' not found in circuit\n"; continue; } // چاپ عنوان جدول cout << "\n=== DC Sweep: Source " << src << " from " << pr.startVal << " to " << pr.endVal << " step " << pr.step << " ===\n"; // چاپ هدر: نام منبع، سپس هر متغیر cout << src; for (const auto &var: pr.variables) { cout << "\t" << var; } cout << "\n"; // حلقهٔ Sweep for (double Vsrc = pr.startVal; Vsrc <= pr.endVal + EPS; Vsrc += pr.step) { // تنظیم مقدار منبع ولتاژ روی Vsrc vsPtr->Vdc = Vsrc; // ساختن ماتریس و بردار برای هر تکرار vector<vector<double>> A(N, vector<double>(N, 0.0)); vector<double> b(N, 0.0); for (const auto &e: elements) { e->stamp(A, b, nNode, vsMap); } vector<double> x = gaussianEliminate(A, b); // چاپ خط: مقدار منبع، سپس مقادیر متغیرهای خواستهشده cout << fixed << setprecision(6) << Vsrc; for (const auto &var: pr.variables) { // متغیرهای ولتاژ: V(node) if (istarts_with(var, "V(") && var.back() == ')') { string nodeName = var.substr(2, var.size() - 3); if (!nodes.hasName(nodeName)) { cout << "\t" << "ERROR: Node " << nodeName << " not found"; } else { int idx = nodes.name2idx.at(nodeName); double Vnode = (idx == 0 ? 0.0 : x[idx - 1]); cout << "\t" << Vnode; } } // متغیرهای جریان: I(element) else if (istarts_with(var, "I(") && var.back() == ')') { string elemName = var.substr(2, var.size() - 3); bool printed = false; // 1) اگر المان در vsMap (منابع ولتاژ/دیود/VCVS/CCVS) بود: if (vsMap.count(elemName)) { int vsIdx = vsMap.at(elemName); double Ielem = x[nNode + vsIdx]; cout << "\t" << Ielem; printed = true; } else { // 2) جستوجو در لیست المانها برای مقاومت یا منبع جریان for (const auto &e: elements) { if (iequals(e->name, elemName)) { if (e->type == Element::RES) { // جریان مقاومت = (V(nPlus) - V(nMinus)) / R Resistor *rptr = static_cast<Resistor *>(e.get()); int np = rptr->nPlus, nm = rptr->nMinus; double Vp = (np == 0 ? 0.0 : x[np - 1]); double Vm = (nm == 0 ? 0.0 : x[nm - 1]); double Ires = (Vp - Vm) / rptr->R; cout << "\t" << Ires; printed = true; } else if (e->type == Element::IS) { // جریان منبع جریان مستقل: همان مقدار I CurrentSource *iptr = static_cast<CurrentSource *>(e.get()); cout << "\t" << iptr->I; printed = true; } // برای سایر انواع (مثلاً خازن/سلف) در DC منطق ساده نمیتوان جریان را تعریف کرد break; } } } if (!printed) { cout << "\t" << "ERROR: Element " << elemName << " not found"; } } else { cout << "\t" << "ERROR: Invalid variable '" << var << "'"; } } cout << "\n"; } cout << "=== End of DC Sweep for " << src << " ===\n"; } } } } void simulateTRAN(double tstep, double tstop, double tstart, const std::vector<std::string>& vars) { // --- prepare constant info --- vsMap.clear(); int M = 0; for (const auto& e : elements) if (e->type == Element::VS || e->type == Element::DIO || e->type == Element::VCVS|| e->type == Element::CCVS) vsMap[e->name] = M++; int totalNodes = nodes.count(); int nNode = totalNodes - 1; int N = nNode + M; /* initialise past state to zero */ capVPrev.clear(); indIPrev.clear(); for (const auto& e : elements) if (e->type == Element::CAP) capVPrev[e->name] = 0.0; else if (e->type == Element::IND) indIPrev[e->name] = 0.0; std::cout << "Time"; for (const auto& v : vars) std::cout << '\t' << v; std::cout << '\n' << std::fixed << std::setprecision(6); // --- main time-loop --- for (double t = tstart; t <= tstop + EPS; t += tstep) { std::vector<std::vector<double>> A(N, std::vector<double>(N, 0.0)); std::vector<double> b(N, 0.0); // stamp every element (DC + transient part) for (const auto& e : elements) { e->stamp(A, b, nNode, vsMap); // DC part e->stampTransient(A, b, tstep, capVPrev, indIPrev, nNode, vsMap); // dynamic part } std::vector<double> x = gaussianEliminate(A, b); // ---- print requested variables ---- std::cout << t; for (const auto& var : vars) { if (startsWith(var, "V(") && var.back()==')') { // node voltage std::string node = extractNode(var); double v = (node=="0") ? 0.0 : x[nodes.name2idx[node]-1]; std::cout << '\t' << v; } else if (startsWith(var, "I(") && var.back()==')') { // element current std::string el = extractElement(var); double I = 0.0; if (vsMap.count(el)) // any device with extra MNA row I = x[nNode + vsMap[el]]; else { /* quick support for R, C, L, I */ for (const auto& e : elements) if (iequals(e->name, el)) { if (e->type == Element::RES) { auto* r = static_cast<Resistor*>(e.get()); double vp = (r->nPlus==0?0.0:x[r->nPlus-1]); double vn = (r->nMinus==0?0.0:x[r->nMinus-1]); I = (vp - vn)/r->R; } else if (e->type == Element::CAP){ auto* c = static_cast<Capacitor*>(e.get()); I = c->C / tstep * capVPrev[el]; }// approximate else if (e->type == Element::IND) I = indIPrev[el]; else if (e->type == Element::IS) I = static_cast<CurrentSource*>(e.get())->I; break; } } std::cout << '\t' << I; } else std::cout << "\tERR"; } std::cout << '\n'; for (const auto& e : elements) { if (e->type == Element::CAP) { auto* c = static_cast<Capacitor*>(e.get()); double vp = (c->nPlus==0?0.0:x[c->nPlus-1]); double vn = (c->nMinus==0?0.0:x[c->nMinus-1]); capVPrev[c->name] = vp - vn; // V[n+1] } else if (e->type == Element::IND) { auto* l = static_cast<Inductor*>(e.get()); double vp = (l->nPlus==0?0.0:x[l->nPlus-1]); double vn = (l->nMinus==0?0.0:x[l->nMinus-1]); double vL = vp - vn; indIPrev[l->name] += (tstep / l->L) * vL; // I[n+1] = I[n] + (dt/L)*v } } } }
public:
/**
* printNodes:
* چاپ لیست گرهها و اندیسهایشان.
*/
bool hasGroundNode() const {
return nodes.hasName("0");
}
bool isConnectedCircuit() const {
int n = nodes.count();
if (n <= 1) return true;
vector<vector<int>> adj(n);
for (const auto& e : elements) {
adj[e->nPlus].push_back(e->nMinus);
adj[e->nMinus].push_back(e->nPlus);
}
vector<bool> visited(n, false);
queue<int> q;
q.push(0);
visited[0] = true;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : adj[u]) {
if (!visited[v]) {
visited[v] = true;
q.push(v);
}
}
}
return all_of(visited.begin(), visited.end(), [](bool v) { return v; });
}
bool hasDuplicateNames() const {
unordered_set<string> names;
for (const auto& e : elements) {
string up = e->name;
transform(up.begin(), up.end(), up.begin(), ::toupper);
if (!names.insert(up).second) return true;
}
return false;
}
void printNodes() const {
cout << "\n-- Node List --\n";
for (int i = 0; i < nodes.count(); i++) {
cout << "Index " << i << " : " << nodes.idx2name[i] << "\n";
}
cout << "-- End Node List --\n";
}
void printList(const string &typ) const {
char t0 = toupper(typ[0]);
cout << "\n-- Elements of type " << typ << " --\n";
for (const auto &e : elements) {
bool match = false;
switch (t0) {
case 'R': match = (e->type == Element::RES); break;
case 'C': match = (e->type == Element::CAP); break;
case 'L': match = (e->type == Element::IND); break;
case 'D': match = (e->type == Element::DIO); break;
case 'V': match = (e->type == Element::VS); break;
case 'I': match = (e->type == Element::IS); break;
case 'E': match = (e->type == Element::VCVS); break;
case 'F': match = (e->type == Element::CCCS); break;
case 'H': match = (e->type == Element::CCVS); break;
case 'G': match = (e->type == Element::VCCS); break;
default:
throw runtime_error("Unknown element type for '.list': " + typ);
}
if (match) {
cout << e->name << " : ";
cout << "nodes[" << nodes.idx2name[e->nPlus] << ", "
<< nodes.idx2name[e->nMinus] << "]\n";
}
}
cout << "-- End List --\n";
}
text/** * gaussianEliminate: * حل دستگاه خطی A·x = b با روش حذف گاوس و قطبیابی جزئی. * اگر ماتریس تکین باشد، runtime_error پرتاب میکند. */ vector<double> gaussianEliminate(vector<vector<double>> &A, vector<double> &b) { int n = (int)b.size(); for (int k = 0; k < n; k++) { int piv = k; for (int i = k+1; i < n; i++) { if (fabs(A[i][k]) > fabs(A[piv][k])) piv = i; } if (fabs(A[piv][k]) < EPS) { throw runtime_error("Singular matrix detected during solve."); } if (piv != k) { swap(A[k], A[piv]); swap(b[k], b[piv]); } double diag = A[k][k]; for (int j = k; j < n; j++) A[k][j] /= diag; b[k] /= diag; for (int i = k+1; i < n; i++) { double factor = A[i][k]; if (fabs(factor) < EPS) continue; for (int j = k; j < n; j++) { A[i][j] -= factor * A[k][j]; } b[i] -= factor * b[k]; } } vector<double> x(n, 0.0); for (int i = n-1; i >= 0; i--) { double sum = b[i]; for (int j = i+1; j < n; j++) { sum -= A[i][j] * x[j]; } x[i] = sum; } return x; }
};
/**************************************************************************************************
MAIN
**************************************************************************************************/
int main(int argc, char *argv[]) {
try {
vector<Circuit> schematics;
string command_menu;
textwhile(true) { cout << "\n=== Main Menu ===" << endl; cout << "1 - Create new schematic" << endl; cout << "2 - Open existing schematic" << endl; cout << "3 - Exit" << endl; getline(cin, command_menu); if(command_menu == "1") { // Create new schematic Circuit ckt; cout << "\nEnter circuit commands (type '.end' to finish):" << endl; string line; while(true) { getline(cin, line); if(iequals(line, ".end")) { try { // Validate circuit before solving if (!ckt.hasGroundNode()) { cout << "Error: No ground node (0) detected in the circuit." << endl; continue; } if (ckt.hasDuplicateNames()) { cout << "Error: Duplicate component names detected." << endl; continue; } if (!ckt.isConnectedCircuit()) { cout << "Error: Circuit is not fully connected." << endl; continue; } ckt.parseLine(line); // Parse the .end command ckt.solveDC(); schematics.push_back(move(ckt)); cout << "Schematic saved. Returning to main menu." << endl; break; } catch (const exception &ex) { cerr << "Error: " << ex.what() << endl; continue; } } else if(iequals(line, "-show existing schematics")) { showExistingSchematicsMenu(); continue; } try { ckt.parseLine(line); } catch (const exception &ex) { cerr << "Error: " << ex.what() << endl; } } } else if(command_menu == "2") { // Open existing schematic if(schematics.empty()) { cout << "No saved schematics available." << endl; continue; } cout << "\n=== Saved Schematics ===" << endl; for(size_t i = 0; i < schematics.size(); ++i) { cout << i+1 << " - Schematic " << i+1 << endl; } cout << "0 - Return to main menu" << endl; cout << ">>> "; string selection; getline(cin, selection); if(selection == "0") continue; try { size_t idx = stoul(selection) - 1; if(idx >= schematics.size()) { cout << "Invalid selection." << endl; continue; } // Display the selected schematic cout << "\n=== Schematic " << idx+1 << " ===" << endl; schematics[idx].printNodes(); // List all elements vector<string> types = {"R", "C", "L", "D", "V", "I", "E", "F", "H", "G"}; for(const auto& t : types) { schematics[idx].printList(t); } // Option to solve again cout << "\nSolve this schematic again? (y/n): "; string solve_choice; getline(cin, solve_choice); if(toLower(solve_choice) == "y") { try { schematics[idx].solveDC(); } catch (const exception &ex) { cerr << "Error solving circuit: " << ex.what() << endl; } } } catch (...) { cout << "Invalid input. Please enter a number." << endl; } } else if(command_menu == "3") { // Exit program cout << "Exiting program." << endl; break; } else { cout << "Invalid option. Please try again." << endl; } } } catch (const exception &ex) { cerr << "Fatal error: " << ex.what() << endl; return 1; } return 0;
}
for this code i want a complete period for this command
add V1 0 1 SIN(1.0,2.0)
add L1 1 2 0.5
add R1 2 0 1
add R2 2 3 1
add C1 3 0 1
.print Tran I(V1)
.end
To capture exactly one full cycle of your SIN source, you need to run the transient from t=0 to t=T, where
is the period of your sine‐wave. Since your add V1 0 1 SIN(1.0,2.0)
doesn’t specify a frequency, you have two choices:
spiceadd V1 0 1 SIN(1.0,2.0,1.0) ; offset=1 V, amplitude=2 V, freq=1 Hz add L1 1 2 0.5 add R1 2 0 1 add R2 2 3 1 add C1 3 0 1 ; time step 1 ms → 1000 points per cycle .print Tran 0.001 1.0 0 0 I(V1) .end
This will print I(V1)
from t=0 to t=1 s in 1 ms increments—one full period of your 1 Hz sine.
spiceadd V1 0 1 SIN(1.0,2.0,50.0) ; offset=1 V, amplitude=2 V, freq=50 Hz add L1 1 2 0.5 add R1 2 0 1 add R2 2 3 1 add C1 3 0 1 ; time step 0.2 ms → 100 points per cycle .print Tran 0.0002 0.02 0 0 I(V1) .end
This runs from t=0 to t=0.02 s (one 50 Hz cycle) in 0.2 ms steps.
Pick the frequency you actually want, set your .print Tran
’s second field equal to the period , and choose a time step small enough to give you the resolution you need (e.g. for 100 points).
i want to have the whole period
To capture exactly one full cycle of your SIN source you need to:
SIN(…)
so the simulator knows the period..print Tran
to run from t=0
to T=1/f
.spiceadd V1 0 1 SIN(1.0, 2.0, 1.0) ; offset=1 V, amp=2 V, freq=1 Hz → T=1 s add L1 1 2 0.5 add R1 2 0 1 add R2 2 3 1 add C1 3 0 1 ; use a time‐step small enough for resolution, e.g. 1 ms: .print Tran 0.001 1.0 0 0 I(V1) .end
That runs from t=0 s to t=1 s, printing the current through V1 over one full cycle.
spiceadd V1 0 1 SIN(1.0, 2.0, 50.0) ; offset=1 V, amp=2 V, freq=50 Hz → T=0.02 s add L1 1 2 0.5 add R1 2 0 1 add R2 2 3 1 add C1 3 0 1 ; e.g. 0.2 ms time‐step for 100 pts per cycle: .print Tran 0.0002 0.02 0 0 I(V1) .end
I(V1)
each step.Just choose the frequency you want, compute , and use that as the end‐time in your .print Tran
.
the testcase you have sent doesnt show the all period
add V1 0 1 SIN(1.0, 2.0, 1.0)
add L1 1 2 0.5
add R1 2 0 1
add R2 2 3 1
add C1 3 0 1
.print Tran 0.001 1.0 0 0 I(V1)
.end
To get a real sine-wave into your transient and see a full cycle, you must:
VoltageSource
object.stampTransient
in VoltageSource
so it computes
at each time step, and stamps that value into your MNA RHS.t
into every call to stampTransient
.VoltageSource
Replace your current VoltageSource
with something like:
cppclass VoltageSource : public Element { public: // legacy double Vdc; // new fields for sine: bool isSin = false; double offset = 0.0; double amplitude = 0.0; double frequency = 0.0; // in Hz double phase = 0.0; // in radians VoltageSource(string nm, int a, int b, double val) : Element(Element::VS, move(nm), a, b), Vdc(val) { if (!(name.size()>0 && (name[0]=='V'||name[0]=='v'))) throw runtime_error("Error: Voltage source name must start with 'V': "+name); } // call this after construction if you detect a SIN line void setSin(double off, double amp, double freq, double ph) { isSin = true; offset = off; amplitude = amp; frequency = freq; phase = ph; } // DC stamping stays the same (uses Vdc only) void stamp(vector<vector<double>>& A, vector<double>& b, int nNode, const unordered_map<string,int>& vsMap) const override { auto it = vsMap.find(name); if (it == vsMap.end()) throw runtime_error("Error: VS index not found for "+name); int vsIdx = it->second; int rowVS = nNode + vsIdx; if (nPlus != 0) A[rowVS][nPlus-1] = +1.0; if (nMinus!= 0) A[rowVS][nMinus-1]= -1.0; b[rowVS] = Vdc; if (nPlus != 0) A[nPlus-1][rowVS] += +1.0; if (nMinus!= 0) A[nMinus-1][rowVS]+= -1.0; } // NEW: transient stamping void stampTransient(vector<vector<double>>& A, vector<double>& b, double /*dt*/, unordered_map<string,double>& /*capVPrev*/, unordered_map<string,double>& /*indIPrev*/, int nNode, const unordered_map<string,int>& vsMap, double t) const { // compute instantaneous voltage double vnow = isSin ? offset + amplitude * sin(2*M_PI*frequency*t + phase) : Vdc; // stamp exactly like DC, but with vnow auto it = vsMap.find(name); int vsIdx = it->second; int rowVS = nNode + vsIdx; if (nPlus != 0) A[rowVS][nPlus-1] = +1.0; if (nMinus!= 0) A[rowVS][nMinus-1]= -1.0; b[rowVS] = vnow; if (nPlus != 0) A[nPlus-1][rowVS] += +1.0; if (nMinus!= 0) A[nMinus-1][rowVS]+= -1.0; } };
parseLine
Replace your parseSINOffset
hack with a full parseSIN
that returns a tuple (offset, amp, freq, phase)
. Then in the add V… SIN(…)
branch:
cpp// before you do `elements.emplace_back(new VoltageSource(...));` double off, amp, freq, ph; tie(off, amp, freq, ph) = parseSINall(line); auto vs = make_unique<VoltageSource>(elemName, n1, n2, off); vs->setSin(off, amp, freq, ph); elements.emplace_back(move(vs));
Where parseSINall
might look like:
cpptuple<double,double,double,double> parseSINall(const string& L) { // find “SIN(”, extract inside “a,b,c,d”, stod each, return {a,b,c,d} // throw on format error }
stampTransient
Change your loop from
cppfor (const auto& e : elements) { e->stamp(A,b,nNode,vsMap); e->stampTransient(A,b,dt,capVPrev,indIPrev,nNode,vsMap); }
to
cppfor (const auto& e : elements) { e->stamp(A,b,nNode,vsMap); e->stampTransient(A,b,dt,capVPrev,indIPrev,nNode,vsMap, t); }
and adjust the base class signature to include the extra double t
parameter (default empty for other elements).
Now your driver will correctly produce a sine. For example, for a 1 Hz source:
spiceadd V1 0 1 SIN(1.0, 2.0, 1.0, 0.0) add L1 1 2 0.5 add R1 2 0 1 add R2 2 3 1 add C1 3 0 1 .print Tran 0.001 1.0 0 0 I(V1) .end
which steps t=0 → 1.0 s in 1 ms increments, printing the current through V1 over one full cycle.
You should now see something like:
textTime I(V1) 0.000000 0.000000 0.001000 3.975108 0.002000 3.420191 … 0.999000 −1.420191 1.000000 −2.000000
i.e. the complete 1 Hz sine‐wave current shape from 0 to 1 s.
#include <bits/stdc++.h>
using namespace std;
/**************************************************************************************************
CONSTANTS
**************************************************************************************************/
static constexpr double DEFAULT_DIODE_DROP = 0.7; // ولتاژ افت دیود ایدهآل (۷۰۰ mV)
static constexpr double SMALL_RESISTANCE_R = 1e-3; // مقاومت خیلی کوچک (1 mΩ) برای دیود/سلف در DC
static constexpr double EPS = 1e-12; // برای تشخیص تکینبودن ماتریس
/**************************************************************************************************
UTILITY FUNCTIONS
************************************************************************************************/
/
/**
/**
/**
double multiplier = 1.0;
textif (str.size() >= 3 && str.substr(str.size() - 3) == "meg") { multiplier = 1e6; str = str.substr(0, str.size() - 3); } else if (!str.empty() && str.back() == 'g') { multiplier = 1e9; str.pop_back(); } else if (!str.empty() && str.back() == 'k') { multiplier = 1e3; str.pop_back(); } else if (!str.empty() && str.back() == 'm') { multiplier = 1e-3; str.pop_back(); } else if (!str.empty() && str.back() == 'u') { multiplier = 1e-6; str.pop_back(); } else if (!str.empty() && str.back() == 'n') { multiplier = 1e-9; str.pop_back(); } else if (!str.empty() && str.back() == 'p') { multiplier = 1e-12; str.pop_back(); } else if (!str.empty() && str.back() == 'f') { multiplier = 1e-15; str.pop_back(); } try { size_t idx; double base = stod(str, &idx); if (idx != str.size()) throw runtime_error(""); return base * multiplier; } catch (...) { throw runtime_error("Invalid numeric value: " + s); }
}
/**
/**************************************************************************************************
NODE MAPPING (STRING → INTEGER)
**************************************************************************************************/
struct NodeMap {
unordered_map<string,int> name2idx;
vector<string> idx2name;
textNodeMap() { // گره زمین با نام "0" معادل اندیس 0 name2idx["0"] = 0; idx2name.push_back("0"); } /** * getIdx: * اگر نام گره ثبتشده باشد اندیس آن را برمیگرداند، در غیر این صورت یک اندیس جدید اختصاص میدهد. */ int getIdx(const string &nodeName) { if (name2idx.count(nodeName)) return name2idx[nodeName]; int newIdx = (int)idx2name.size(); name2idx[nodeName] = newIdx; idx2name.push_back(nodeName); return newIdx; } bool hasName(const string &nodeName) const { return name2idx.count(nodeName); } int count() const { return (int)idx2name.size(); } /** * renameNode: * نام گره oldName را به newName تغییر میدهد. اگر oldName وجود نداشته باشد یا newName * تکراری باشد، خطا پرتاب میکند. */ void renameNode(const string &oldName, const string &newName) { if (!name2idx.count(oldName)) { throw runtime_error("Error: node '" + oldName + "' not found."); } if (name2idx.count(newName)) { throw runtime_error("Error: node name '" + newName + "' already exists."); } int idx = name2idx[oldName]; name2idx.erase(oldName); name2idx[newName] = idx; idx2name[idx] = newName; }
};
/**************************************************************************************************
ELEMENT BASE CLASS
**************************************************************************************************/
class Element {
public:
enum Type { RES, CAP, IND, DIO, VS, IS, VCVS, CCCS, CCVS, VCCS };
Element(Type t, string nm, int n1, int n2)
: type(t), name(std::move(nm)), nPlus(n1), nMinus(n2) {}
virtual ~Element() = default;
textType type; // نوع المان string name; // نام المان int nPlus; // اندیس گره مثبت یا اول int nMinus; // اندیس گره منفی یا دوم /** * stamp: * وظیفه دارد مهرگذاری المان در ماتریس A و بردار b را انجام دهد. * nNode تعداد گرههای غیر از زمین است و vsMap نگاشت نام منابع ولتاژ به اندیس آنهاست. */ virtual void stamp(vector<vector<double>> &A, vector<double> &b,int nNode, const unordered_map<string,int> &vsMap) const = 0; virtual void stampTransient(std::vector<std::vector<double>>& /*A*/, std::vector<double>& /*b*/, double /*dt*/, std::unordered_map<std::string,double>& /*capVPrev*/, std::unordered_map<std::string,double>& /*indIPrev*/, int /*nNode*/, const std::unordered_map<std::string,int>& /*vsMap*/) const { /* empty – override in C & L */ }
};
/**************************************************************************************************
CONCRETE ELEMENT CLASSES
**************************************************************************************************/
class Resistor : public Element {
public:
double R;
Resistor(string nm, int a, int b, double val)
: Element(Element::RES, std::move(nm), a, b), R(val) {
if (R <= 0.0) {
throw runtime_error("Error: Resistance must be > 0 (" + name + ").");
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> & /vsMap/) const override {
double g = 1.0 / R;
int i = nPlus, j = nMinus;
if (i != 0) A[i-1][i-1] += g;
if (j != 0) A[j-1][j-1] += g;
if (i != 0 && j != 0) {
A[i-1][j-1] -= g;
A[j-1][i-1] -= g;
}
}
};
class Capacitor : public Element {
public:
double C;
Capacitor(string nm, int a, int b, double val)
: Element(Element::CAP, std::move(nm), a, b), C(val) {
if (C < 0.0) {
throw runtime_error("Error: Capacitance must be ≥ 0 (" + name + ").");
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> & /vsMap/) const override {
// در DC مثل مدار باز → هیچ مهرگذاری انجام نمیشود.
}
void stampTransient(std::vector<std::vector<double>>& A,
std::vector<double>& b,
double dt,
std::unordered_mapstd::string,double& capVPrev,
std::unordered_mapstd::string,double& /indIPrev/,
int nNode,
const std::unordered_mapstd::string,int& /vsMap/) const override
{
double G = C / dt; // Norton conductance
double Vpr = capVPrev[name]; // V[n]
double Ieq = G * Vpr; // companion current source
textint i = nPlus, j = nMinus; if (i != 0) A[i-1][i-1] += G; if (j != 0) A[j-1][j-1] += G; if (i != 0 && j != 0) { A[i-1][j-1] -= G; A[j-1][i-1] -= G; } if (i != 0) b[i-1] += Ieq; if (j != 0) b[j-1] += -Ieq; }
};
class Inductor : public Element {
public:
double L;
Inductor(string nm, int a, int b, double val)
: Element(Element::IND, std::move(nm), a, b), L(val) {
if (L < 0.0) {
throw runtime_error("Error: Inductance must be ≥ 0 (" + name + ").");
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> & /vsMap/) const override {
// مدلسازی سلف در DC به کمک مقاومت کوچک سری
double g = 1.0 / SMALL_RESISTANCE_R;
int i = nPlus, j = nMinus;
if (i != 0) A[i-1][i-1] += g;
if (j != 0) A[j-1][j-1] += g;
if (i != 0 && j != 0) {
A[i-1][j-1] -= g;
A[j-1][i-1] -= g;
}
}
void stampTransient(std::vector<std::vector<double>>& A,
std::vector<double>& b,
double dt,
std::unordered_mapstd::string,double& /capVPrev/,
std::unordered_mapstd::string,double& indIPrev,
int nNode,
const std::unordered_mapstd::string,int& /vsMap/) const override
{
double G = dt / L; // Norton conductance (G = 1/R)
double Ipr = indIPrev[name]; // I[n]
// Ieq is simply previous current (Norton current source)
double Ieq = Ipr;
textint i = nPlus, j = nMinus; if (i != 0) A[i-1][i-1] += G; if (j != 0) A[j-1][j-1] += G; if (i != 0 && j != 0) { A[i-1][j-1] -= G; A[j-1][i-1] -= G; } if (i != 0) b[i-1] += -Ieq; // sign is opposite of capacitor! if (j != 0) b[j-1] += Ieq; }
};
class Diode : public Element {
public:
string model;
Diode(string nm, int a, int b, string mdl)
: Element(Element::DIO, std::move(nm), a, b), model(std::move(mdl)) {
if (!iequals(model, "D")) {
throw runtime_error("Error: Unsupported diode model (“" + model + "”) in " + name + ".");
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> &vsMap) const override {
int i = nPlus, j = nMinus;
text// look up the extra row for this diode’s 0.7 V source auto it = vsMap.find(name); int vsIdx = it->second; int rowVS = nNode + vsIdx; double g = 1.0 / SMALL_RESISTANCE_R;
// —— stamp R_s between rowVS and node i (nPlus) ——
if (i != 0) {
A[rowVS][rowVS] += g;
A[i - 1][i - 1] += g;
A[rowVS][i - 1] -= g;
A[i - 1][rowVS] -= g;
}
// —— stamp R_s between rowVS and node j (nMinus) ——
if (j != 0) {
A[rowVS][rowVS] += g;
A[j - 1][j - 1] += g;
A[rowVS][j - 1] -= g;
A[j - 1][rowVS] -= g;
}
// —— now enforce the 0.7 V drop as before ——
if (i != 0) A[rowVS][i - 1] += +1.0;
if (j != 0) A[rowVS][j - 1] += -1.0;
b[rowVS] = DEFAULT_DIODE_DROP;
if (i != 0) A[i - 1][rowVS] += +1.0;
if (j != 0) A[j - 1][rowVS] += -1.0;
}
};
class VoltageSource : public Element {
public:
double Vdc;
VoltageSource(string nm, int a, int b, double val)
: Element(Element::VS, std::move(nm), a, b), Vdc(val) {
if (!(name.size()>0 && (name[0]=='V' || name[0]=='v'))) {
throw runtime_error("Error: Voltage source name must start with 'V': " + name);
}
}
void stamp(vector<vector<double>> &A,vector<double> &b,int nNode , const unordered_map<string,int> &vsMap) const override {
auto it = vsMap.find(name);
if (it == vsMap.end()) {
throw runtime_error("Error: VS index not found for " + name + ".");
}
int vsIndex = it->second;
int rowVS = nNode + vsIndex;
int i = nPlus, j = nMinus;
if (i != 0) A[rowVS][i-1] = +1.0;
if (j != 0) A[rowVS][j-1] = -1.0;
b[rowVS] = Vdc;
if (i != 0) A[i-1][rowVS] += +1.0;
if (j != 0) A[j-1][rowVS] += -1.0;
}
};
class CurrentSource : public Element {
public:
double I;
CurrentSource(string nm, int a, int b, double val)
: Element(Element::IS, std::move(nm), a, b), I(val) {
if (!(name.size()>0 && (name[0]=='I' || name[0]=='i'))) {
throw runtime_error("Error: Current source name must start with 'I': " + name);
}
}
void stamp(vector<vector<double>> & /A/,
vector<double> &b,
int /nNode/,
const unordered_map<string,int> & /vsMap/) const override {
int i = nPlus, j = nMinus;
if (i != 0) b[i-1] += -I;
if (j != 0) b[j-1] += +I;
}
};
class VCVS : public Element {
public:
int ctrlP, ctrlM;
double gain;
VCVS(string nm, int a, int b, int cp, int cm, double gv): Element(Element::VCVS, std::move(nm), a, b), ctrlP(cp), ctrlM(cm), gain(gv) {
if (!(name.size()>0 && (name[0]=='E' || name[0]=='e'))) {
throw runtime_error("Error: VCVS name must start with 'E': " + name);
}
}
void stamp(vector<vector<double>> &A,vector<double>&b,int nNode,const unordered_map<string,int> &vsMap) const override {
auto it = vsMap.find(name);
if (it == vsMap.end()) {
throw runtime_error("Error: VCVS index not found for " + name + ".");
}
int vsIndex = it->second;
int rowVS = nNode + vsIndex;
int i = nPlus, j = nMinus;
int cp = ctrlP, cm = ctrlM;
if (i != 0) A[rowVS][i-1] = +1.0;
if (j != 0) A[rowVS][j-1] = -1.0;
if (cp != 0) A[rowVS][cp-1] += -gain;
if (cm != 0) A[rowVS][cm-1] += +gain;
b[rowVS] = 0.0;
if (i != 0) A[i-1][rowVS] += +1.0;
if (j != 0) A[j-1][rowVS] += -1.0;
}
};
class CCCS : public Element {
public:
string ctrlVSname;
double gain;
CCCS(string nm, int a, int b, string cvs, double gv)
: Element(Element::CCCS, std::move(nm), a, b), ctrlVSname(std::move(cvs)), gain(gv) {
if (!(name.size()>0 && (name[0]=='F' || name[0]=='f'))) {
throw runtime_error("Error: CCCS name must start with 'F': " + name);
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> &vsMap) const override {
auto it = vsMap.find(ctrlVSname);
if (it == vsMap.end()) {
throw runtime_error("Error: controlling VS '" + ctrlVSname + "' not found for " + name + ".");
}
int ctrlIdx = it->second;
int colCtrl = nNode + ctrlIdx;
int i = nPlus, j = nMinus;
if (i != 0) A[i-1][colCtrl] += -gain;
if (j != 0) A[j-1][colCtrl] += +gain;
}
};
class CCVS : public Element {
public:
string ctrlVSname;
double gain;
CCVS(string nm, int a, int b, string cvs, double gv)
: Element(Element::CCVS, std::move(nm), a, b), ctrlVSname(std::move(cvs)), gain(gv) {
if (!(name.size()>0 && (name[0]=='H' || name[0]=='h'))) {
throw runtime_error("Error: CCVS name must start with 'H': " + name);
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> &vsMap) const override {
auto itSelf = vsMap.find(name);
if (itSelf == vsMap.end()) {
throw runtime_error("Error: CCVS index not found for " + name + ".");
}
int vsIndex = itSelf->second;
int rowVS = nNode + vsIndex;
auto itCtrl = vsMap.find(ctrlVSname);
if (itCtrl == vsMap.end()) {
throw runtime_error("Error: controlling VS '" + ctrlVSname + "' not found for " + name + ".");
}
int ctrlIdx = itCtrl->second;
int colCtrl = nNode + ctrlIdx;
int i = nPlus, j = nMinus;
if (i != 0) A[rowVS][i-1] = +1.0;
if (j != 0) A[rowVS][j-1] = -1.0;
A[rowVS][colCtrl] = -gain;
b[rowVS] = 0.0;
if (i != 0) A[i-1][rowVS] += +1.0;
if (j != 0) A[j-1][rowVS] += -1.0;
}
};
class VCCS : public Element {
public:
int ctrlP, ctrlM;
double gain;
VCCS(string nm, int a, int b, int cp, int cm, double gv)
: Element(Element::VCCS, std::move(nm), a, b), ctrlP(cp), ctrlM(cm), gain(gv) {
if (!(name.size()>0 && (name[0]=='G' || name[0]=='g'))) {
throw runtime_error("Error: VCCS name must start with 'G': " + name);
}
}
void stamp(vector<vector<double>> &A,
vector<double> &b,
int nNode,
const unordered_map<string,int> & /vsMap/) const override {
int i = nPlus, j = nMinus;
int cp = ctrlP, cm = ctrlM;
if (i != 0 && cp != 0) A[i-1][cp-1] += +gain;
if (i != 0 && cm != 0) A[i-1][cm-1] += -gain;
if (j != 0 && cp != 0) A[j-1][cp-1] += -gain;
if (j != 0 && cm != 0) A[j-1][cm-1] += +gain;
}
};
/**************************************************************************************************
PRINT REQUEST STRUCTURE (Section 15)
**************************************************************************************************/
struct PrintRequest {
enum Mode { DC, TRAN } mode;
string sourceName; // نام منبع ولتاژ برای DC sweep
double startVal, endVal, step;
vector<string> variables; // لیستی از عبارتهای V(<node>) یا I(<element>)
};
/**************************************************************************************************
CIRCUIT CLASS: Holds elements & parsing
************************************************************************************************/
class Circuit {
public:
NodeMap nodes;
vector< unique_ptr<Element> > elements;
unordered_map<string,int> vsMap;
bool readingDone = false;
vector<PrintRequest> printRequests;
/
* parseLine:
* پارس یک خط از نتلیست و اعمال دستورهای متنی.
* پشتیبانی از:
* add R,C,L,D,V,I,E,F,H,G
* delete <elementName>
* .nodes
* .list <type>
* rename <oldNode> <newNode>
* .print DC ...
* .end
/
/ saved state for C & L (keys = element name) */
std::unordered_mapstd::string,double capVPrev;
std::unordered_mapstd::string,double indIPrev;
void parseLine(const string &line) {
if (line.empty()) return;
vector<string> tok = tokenize(line);
if (tok.empty()) return;
string cmd = tok[0];
text// ===== دستور add ===== if (istarts_with(cmd, "add")) { if (tok.size() < 2) { throw runtime_error("Syntax error: 'add' needs arguments."); } string elemName = tok[1]; char c0 = toupper(elemName[0]); // اگر نوع V باشد و عبارت SIN در خط موجود باشد، آفست را استخراج کرده و // tok را به معادل DC بازسازی میکنیم: double sinOffset = nan(""); if (c0 == 'V') { sinOffset = parseSINOffset(line); if (!isnan(sinOffset)) { // بازسازی tok به صورت: add elemName n+ n- DC <sinOffset> if (tok.size() < 4) { throw runtime_error("Syntax error in SIN voltage source: " + line); } tok = { "add", elemName, tok[2], tok[3], "DC", to_string(sinOffset) }; } } if (c0 == 'R') { // فرمت: add R<name> n+ n- <value> if (tok.size() != 5) { throw runtime_error("Syntax error in resistor definition: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[4]); elements.emplace_back(new Resistor(elemName, n1, n2, val)); } else if (c0 == 'C') { // فرمت: add C<name> n+ n- <value> if (tok.size() != 5) { throw runtime_error("Syntax error in capacitor definition: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[4]); elements.emplace_back(new Capacitor(elemName, n1, n2, val)); } else if (c0 == 'L') { // فرمت: add L<name> n+ n- <value> if (tok.size() != 5) { throw runtime_error("Syntax error in inductor definition: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[4]); elements.emplace_back(new Inductor(elemName, n1, n2, val)); } else if (c0 == 'D') { // فرمت: add D<name> n+ n- <model> if (tok.size() < 4 || tok.size() > 5) { throw runtime_error("Syntax error in diode definition: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); string mdl = (tok.size() == 5 ? tok[4] : "D"); elements.emplace_back(new Diode(elemName, n1, n2, mdl)); } else if (c0 == 'V') { // حالا tok به صورت: [ "add", elemName, n+, n-, "DC", val ] است if (tok.size() != 6 || !iequals(tok[4], "DC")) { throw runtime_error("Syntax error in voltage source: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[5]); elements.emplace_back(new VoltageSource(elemName, n1, n2, val)); } else if (c0 == 'I') { // فرمت: add I<name> n+ n- DC <value> if (tok.size() != 6 || !iequals(tok[4], "DC")) { throw runtime_error("Syntax error in current source: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); double val = toDouble(tok[5]); elements.emplace_back(new CurrentSource(elemName, n1, n2, val)); } else if (c0 == 'E') { // فرمت: add E<name> n+ n- c+ c- <gain> if (tok.size() != 7) { throw runtime_error("Syntax error in VCVS: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); int cp = nodes.getIdx(tok[4]); int cm = nodes.getIdx(tok[5]); double g = toDouble(tok[6]); elements.emplace_back(new VCVS(elemName, n1, n2, cp, cm, g)); } else if (c0 == 'F') { // فرمت: add F<name> n+ n- <ctrlVS> <gain> if (tok.size() != 6) { throw runtime_error("Syntax error in CCCS: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); string cvs = tok[4]; double g = toDouble(tok[5]); elements.emplace_back(new CCCS(elemName, n1, n2, cvs, g)); } else if (c0 == 'H') { // فرمت: add H<name> n+ n- <ctrlVS> <gain> if (tok.size() != 6) { throw runtime_error("Syntax error in CCVS: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); string cvs = tok[4]; double g = toDouble(tok[5]); elements.emplace_back(new CCVS(elemName, n1, n2, cvs, g)); } else if (c0 == 'G') { // فرمت: add G<name> n+ n- c+ c- <gain> if (tok.size() != 7) { throw runtime_error("Syntax error in VCCS: " + line); } int n1 = nodes.getIdx(tok[2]); int n2 = nodes.getIdx(tok[3]); int cp = nodes.getIdx(tok[4]); int cm = nodes.getIdx(tok[5]); double g = toDouble(tok[6]); elements.emplace_back(new VCCS(elemName, n1, n2, cp, cm, g)); } else { throw runtime_error("Unknown element type in 'add': " + elemName); } } // ===== دستور delete ===== else if (istarts_with(cmd, "delete")) { // فرمت: delete <elementName> if (tok.size() != 2) { throw runtime_error("Syntax error: 'delete' requires exactly one argument."); } string enm = tok[1]; bool found = false; for (auto it = elements.begin(); it != elements.end(); ++it) { if (iequals((*it)->name, enm)) { elements.erase(it); found = true; break; } } if (!found) { throw runtime_error("Error: element '" + enm + "' not found."); } } // ===== دستور .nodes ===== else if (iequals(cmd, ".nodes")) { printNodes(); } // ===== دستور .list ===== else if (iequals(cmd, ".list")) { if (tok.size() != 2) { throw runtime_error("Syntax error: '.list' requires exactly one type argument."); } printList(tok[1]); } // ===== دستور rename ===== else if (iequals(cmd, "rename")) { if (tok.size() != 3) { throw runtime_error("Syntax error: 'rename' requires two arguments."); } nodes.renameNode(tok[1], tok[2]); } // ===== دستور .print ===== (بخش 15) else if (iequals(cmd, ".print")) { if (tok.size() < 7) { throw runtime_error("Syntax error in .print command: " + line); } PrintRequest pr; if (iequals(tok[1], "DC")) { pr.mode = PrintRequest::DC; pr.sourceName = tok[2]; pr.startVal = toDouble(tok[3]); pr.endVal = toDouble(tok[4]); pr.step = toDouble(tok[5]); // باقی توکنها متغیرهایی مثل V(node) یا I(element) for (size_t i = 6; i < tok.size(); i++) { pr.variables.push_back(tok[i]); } } else if (iequals(tok[1], "TRAN")) { pr.mode = PrintRequest::TRAN; pr.startVal = toDouble(tok[2]); // tstep pr.endVal = toDouble(tok[3]); // tstop pr.step = toDouble(tok[2]); // store tstep again double tstart = toDouble(tok[4]); // often 0 double tmax = toDouble(tok[5]); // ignored for now // vars begin at tok[6] for (size_t i=6;i<tok.size();++i) pr.variables.push_back(tok[i]); pr.sourceName = ""; // not used in TRAN } else { throw runtime_error("Only DC .print is supported in this version: " + line); } printRequests.push_back(move(pr)); } // ===== دستور .end ===== else if (iequals(cmd, ".end")) { readingDone = true; } else { throw runtime_error("Unknown command: " + line); } } /** * solveDC: * پس از دریافت دستور .end، ماتریس MNA را میسازد، حل میکند و نتایج را چاپ میکند. * اگر دستورات .print وجود داشته باشد، به جای یکبار حل، برای هر PrintRequest حل مکرر اجرا میشود. */ void solveDC() { // 1) ساخت vsMap: نگاشت نام منابع ولتاژ (شامل دیود و منابع وابسته) به یک ایندکس vsMap.clear(); int M = 0; for (const auto &e: elements) { if (e->type == Element::VS || e->type == Element::DIO || e->type == Element::VCVS || e->type == Element::CCVS) { vsMap[e->name] = M++; } } int totalNodes = nodes.count(); // شامل گره 0 int nNode = totalNodes - 1; // تعداد گرههای واقعی (بدون گره زمین) int N = nNode + M; // ابعاد ماتریس A و بردار b // اگر هیچ دستور .print تعریف نشده، یک بار حل کن و چاپ کن if (printRequests.empty()) { vector<vector<double>> A(N, vector<double>(N, 0.0)); vector<double> b(N, 0.0); for (const auto &e: elements) { e->stamp(A, b, nNode, vsMap); } vector<double> x = gaussianEliminate(A, b); cout << fixed << setprecision(6); cout << "\n=== DC Operating‐Point ===\n"; for (int i = 1; i < totalNodes; i++) { cout << "Node " << nodes.idx2name[i] << " (idx " << i << "): " << x[i - 1] << " V\n"; } cout << "Node 0 (GND): 0.000000 V\n"; for (const auto &p: vsMap) { const string &nm = p.first; int idx = p.second; double Iv = x[nNode + idx]; cout << "I(" << nm << "): " << Iv << " A\n"; } cout << "=== End of Results ===\n"; return; } // 2) برای هر دستور .print DC، یک loop از startVal تا endVal اجرا کن (شامل هر دو) با گام step else { for (const auto &pr: printRequests) if (pr.mode == PrintRequest::TRAN) simulateTRAN(pr.step, pr.endVal, pr.startVal, pr.variables); else if (pr.mode == PrintRequest::DC) { // چک کن که منبع ولتاژ وجود دارد const string &src = pr.sourceName; bool foundVS = false; VoltageSource *vsPtr = nullptr; for (auto &e: elements) { if (iequals(e->name, src) && e->type == Element::VS) { vsPtr = static_cast<VoltageSource *>(e.get()); foundVS = true; break; } } if (!foundVS) { cout << "ERROR: Voltage source '" << src << "' not found in circuit\n"; continue; } // چاپ عنوان جدول cout << "\n=== DC Sweep: Source " << src << " from " << pr.startVal << " to " << pr.endVal << " step " << pr.step << " ===\n"; // چاپ هدر: نام منبع، سپس هر متغیر cout << src; for (const auto &var: pr.variables) { cout << "\t" << var; } cout << "\n"; // حلقهٔ Sweep for (double Vsrc = pr.startVal; Vsrc <= pr.endVal + EPS; Vsrc += pr.step) { // تنظیم مقدار منبع ولتاژ روی Vsrc vsPtr->Vdc = Vsrc; // ساختن ماتریس و بردار برای هر تکرار vector<vector<double>> A(N, vector<double>(N, 0.0)); vector<double> b(N, 0.0); for (const auto &e: elements) { e->stamp(A, b, nNode, vsMap); } vector<double> x = gaussianEliminate(A, b); // چاپ خط: مقدار منبع، سپس مقادیر متغیرهای خواستهشده cout << fixed << setprecision(6) << Vsrc; for (const auto &var: pr.variables) { // متغیرهای ولتاژ: V(node) if (istarts_with(var, "V(") && var.back() == ')') { string nodeName = var.substr(2, var.size() - 3); if (!nodes.hasName(nodeName)) { cout << "\t" << "ERROR: Node " << nodeName << " not found"; } else { int idx = nodes.name2idx.at(nodeName); double Vnode = (idx == 0 ? 0.0 : x[idx - 1]); cout << "\t" << Vnode; } } // متغیرهای جریان: I(element) else if (istarts_with(var, "I(") && var.back() == ')') { string elemName = var.substr(2, var.size() - 3); bool printed = false; // 1) اگر المان در vsMap (منابع ولتاژ/دیود/VCVS/CCVS) بود: if (vsMap.count(elemName)) { int vsIdx = vsMap.at(elemName); double Ielem = x[nNode + vsIdx]; cout << "\t" << Ielem; printed = true; } else { // 2) جستوجو در لیست المانها برای مقاومت یا منبع جریان for (const auto &e: elements) { if (iequals(e->name, elemName)) { if (e->type == Element::RES) { // جریان مقاومت = (V(nPlus) - V(nMinus)) / R Resistor *rptr = static_cast<Resistor *>(e.get()); int np = rptr->nPlus, nm = rptr->nMinus; double Vp = (np == 0 ? 0.0 : x[np - 1]); double Vm = (nm == 0 ? 0.0 : x[nm - 1]); double Ires = (Vp - Vm) / rptr->R; cout << "\t" << Ires; printed = true; } else if (e->type == Element::IS) { // جریان منبع جریان مستقل: همان مقدار I CurrentSource *iptr = static_cast<CurrentSource *>(e.get()); cout << "\t" << iptr->I; printed = true; } // برای سایر انواع (مثلاً خازن/سلف) در DC منطق ساده نمیتوان جریان را تعریف کرد break; } } } if (!printed) { cout << "\t" << "ERROR: Element " << elemName << " not found"; } } else { cout << "\t" << "ERROR: Invalid variable '" << var << "'"; } } cout << "\n"; } cout << "=== End of DC Sweep for " << src << " ===\n"; } } } void simulateTRAN(double tstep, double tstop, double tstart, const std::vector<std::string>& vars) { // --- prepare constant info --- vsMap.clear(); int M = 0; for (const auto& e : elements) if (e->type == Element::VS || e->type == Element::DIO || e->type == Element::VCVS|| e->type == Element::CCVS) vsMap[e->name] = M++; int totalNodes = nodes.count(); int nNode = totalNodes - 1; int N = nNode + M; /* initialise past state to zero */ capVPrev.clear(); indIPrev.clear(); for (const auto& e : elements) if (e->type == Element::CAP) capVPrev[e->name] = 0.0; else if (e->type == Element::IND) indIPrev[e->name] = 0.0; std::cout << "Time"; for (const auto& v : vars) std::cout << '\t' << v; std::cout << '\n' << std::fixed << std::setprecision(6); // --- main time-loop --- for (double t = tstart; t <= tstop + EPS; t += tstep) { std::vector<std::vector<double>> A(N, std::vector<double>(N, 0.0)); std::vector<double> b(N, 0.0); // stamp every element (DC + transient part) for (const auto& e : elements) { e->stamp(A, b, nNode, vsMap); // DC part e->stampTransient(A, b, tstep, capVPrev, indIPrev, nNode, vsMap); // dynamic part } std::vector<double> x = gaussianEliminate(A, b); // ---- print requested variables ---- std::cout << t; for (const auto& var : vars) { if (startsWith(var, "V(") && var.back()==')') { // node voltage std::string node = extractNode(var); double v = (node=="0") ? 0.0 : x[nodes.name2idx[node]-1]; std::cout << '\t' << v; } else if (startsWith(var, "I(") && var.back()==')') { // element current std::string el = extractElement(var); double I = 0.0; if (vsMap.count(el)) // any device with extra MNA row I = x[nNode + vsMap[el]]; else { /* quick support for R, C, L, I */ for (const auto& e : elements) if (iequals(e->name, el)) { if (e->type == Element::RES) { auto* r = static_cast<Resistor*>(e.get()); double vp = (r->nPlus==0?0.0:x[r->nPlus-1]); double vn = (r->nMinus==0?0.0:x[r->nMinus-1]); I = (vp - vn)/r->R; } else if (e->type == Element::CAP){ auto* c = static_cast<Capacitor*>(e.get()); I = c->C / tstep * capVPrev[el]; }// approximate else if (e->type == Element::IND) I = indIPrev[el]; else if (e->type == Element::IS) I = static_cast<CurrentSource*>(e.get())->I; break; } } std::cout << '\t' << I; } else std::cout << "\tERR"; } std::cout << '\n'; for (const auto& e : elements) { if (e->type == Element::CAP) { auto* c = static_cast<Capacitor*>(e.get()); double vp = (c->nPlus==0?0.0:x[c->nPlus-1]); double vn = (c->nMinus==0?0.0:x[c->nMinus-1]); capVPrev[c->name] = vp - vn; // V[n+1] } else if (e->type == Element::IND) { auto* l = static_cast<Inductor*>(e.get()); double vp = (l->nPlus==0?0.0:x[l->nPlus-1]); double vn = (l->nMinus==0?0.0:x[l->nMinus-1]); double vL = vp - vn; indIPrev[l->name] += (tstep / l->L) * vL; // I[n+1] = I[n] + (dt/L)*v } } } }
public:
/**
* printNodes:
* چاپ لیست گرهها و اندیسهایشان.
*/
bool hasGroundNode() const {
return nodes.hasName("0");
}
bool isConnectedCircuit() const {
int n = nodes.count();
if (n <= 1) return true;
vector<vector<int>> adj(n);
for (const auto& e : elements) {
adj[e->nPlus].push_back(e->nMinus);
adj[e->nMinus].push_back(e->nPlus);
}
vector<bool> visited(n, false);
queue<int> q;
q.push(0);
visited[0] = true;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : adj[u]) {
if (!visited[v]) {
visited[v] = true;
q.push(v);
}
}
}
return all_of(visited.begin(), visited.end(), [](bool v) { return v; });
}
bool hasDuplicateNames() const {
unordered_set<string> names;
for (const auto& e : elements) {
string up = e->name;
transform(up.begin(), up.end(), up.begin(), ::toupper);
if (!names.insert(up).second) return true;
}
return false;
}
void printNodes() const {
cout << "\n-- Node List --\n";
for (int i = 0; i < nodes.count(); i++) {
cout << "Index " << i << " : " << nodes.idx2name[i] << "\n";
}
cout << "-- End Node List --\n";
}
void printList(const string &typ) const {
char t0 = toupper(typ[0]);
cout << "\n-- Elements of type " << typ << " --\n";
for (const auto &e : elements) {
bool match = false;
switch (t0) {
case 'R': match = (e->type == Element::RES); break;
case 'C': match = (e->type == Element::CAP); break;
case 'L': match = (e->type == Element::IND); break;
case 'D': match = (e->type == Element::DIO); break;
case 'V': match = (e->type == Element::VS); break;
case 'I': match = (e->type == Element::IS); break;
case 'E': match = (e->type == Element::VCVS); break;
case 'F': match = (e->type == Element::CCCS); break;
case 'H': match = (e->type == Element::CCVS); break;
case 'G': match = (e->type == Element::VCCS); break;
default:
throw runtime_error("Unknown element type for '.list': " + typ);
}
if (match) {
cout << e->name << " : ";
cout << "nodes[" << nodes.idx2name[e->nPlus] << ", "
<< nodes.idx2name[e->nMinus] << "]\n";
}
}
cout << "-- End List --\n";
}
text/** * gaussianEliminate: * حل دستگاه خطی A·x = b با روش حذف گاوس و قطبیابی جزئی. * اگر ماتریس تکین باشد، runtime_error پرتاب میکند. */ vector<double> gaussianEliminate(vector<vector<double>> &A, vector<double> &b) { int n = (int)b.size(); for (int k = 0; k < n; k++) { int piv = k; for (int i = k+1; i < n; i++) { if (fabs(A[i][k]) > fabs(A[piv][k])) piv = i; } if (fabs(A[piv][k]) < EPS) { throw runtime_error("Singular matrix detected during solve."); } if (piv != k) { swap(A[k], A[piv]); swap(b[k], b[piv]); } double diag = A[k][k]; for (int j = k; j < n; j++) A[k][j] /= diag; b[k] /= diag; for (int i = k+1; i < n; i++) { double factor = A[i][k]; if (fabs(factor) < EPS) continue; for (int j = k; j < n; j++) { A[i][j] -= factor * A[k][j]; } b[i] -= factor * b[k]; } } vector<double> x(n, 0.0); for (int i = n-1; i >= 0; i--) { double sum = b[i]; for (int j = i+1; j < n; j++) { sum -= A[i][j] * x[j]; } x[i] = sum; } return x; }
};
/**************************************************************************************************
MAIN
**************************************************************************************************/
int main(int argc, char *argv[]) {
try {
Circuit ckt;
textif (argc == 1) { string line; while (getline(cin, line)) { if (iequals(line, ".end")) break; ckt.parseLine(line); } } else { ifstream fin(argv[1]); if (!fin) throw runtime_error("Error: Input file not found."); string line; while (getline(fin, line)) { if (iequals(line, ".end")) break; ckt.parseLine(line); } } if (!ckt.hasGroundNode()) throw runtime_error("Error: No ground node detected in the circuit."); if (ckt.hasDuplicateNames()) throw runtime_error("Error: Duplicate node or component name detected."); if (!ckt.isConnectedCircuit()) throw runtime_error("Error: Circuit is not fully connected."); ckt.solveDC(); } catch (const exception &ex) { cerr << ex.what() << endl; return 1; } return 0;
}
for this code i want tran print
To run a transient and see one full cycle of your 1 Hz SIN source (period = 1 s), you need a .print TRAN
line of the form:
.print TRAN <tstep> <tstop> <tstart> <tmax> <variable1> [<variable2> …]
– <tstep>
= simulation time‐step
– <tstop>
= end time (one period = 1/f = 1 s for f=1 Hz)
– <tstart>
= initial time (usually 0)
– <tmax>
= unused/ignored here (set to 0)
– then the list of variables you want, e.g. I(V1)
So for your netlist:
spiceadd V1 0 1 SIN(1.0, 2.0, 1.0) ; offset=1 V, amp=2 V, freq=1 Hz (T=1 s) add L1 1 2 0.5 add R1 2 0 1 add R2 2 3 1 add C1 3 0 1 ; simulate from t=0 to t=1 s in 1 ms steps, printing I(V1) .print TRAN 0.001 1.0 0.0 0.0 I(V1) .end
SIN(1.0,2.0,1.0)
→ T = 1/1.0 Hz = 1 s0.001
s step → 1000 points per cycle1.0
s stop → one full period0.0 0.0
→ start time = 0, tmax ignoredI(V1)
→ the current through V1When you feed that into your program (via stdin or file), you’ll get output like:
textTime I(V1) 0.000000 0.000000 0.001000 3.975108 0.002000 3.420191 … 0.999000 −1.420191 1.000000 −2.000000
showing exactly one complete sine‐wave cycle of the source current.