for this code give me test case to have the sin vo...

Created on: June 10, 2025

Answered using O4 Mini High by Chat01

Question

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
    

************************************************************************************************/
/

  • tokenize:
  • ورودی خط را بر حسب فضاهای خالی تقسیم می‌کند و لیست کلمات حاصل را بازمی‌گرداند.
    */
    // 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;
    }

/**

  • iequals:
  • مقایسهٔ دو رشته به صورت case-insensitive.
    */
    static bool iequals(const string &a, const string &b) {
    if (a.size() != b.size()) return false;
    for (size_t i = 0; i < a.size(); i++) {
    if (tolower(a[i]) != tolower(b[i])) return false;
    }
    return true;
    }

/**

  • istarts_with:
  • بررسی می‌کند آیا رشته s با prefix به صورت case-insensitive شروع می‌شود یا خیر.
    */
    static bool istarts_with(const string &s, const string &prefix) {
    if (s.size() < prefix.size()) return false;
    for (size_t i = 0; i < prefix.size(); i++) {
    if (tolower(s[i]) != tolower(prefix[i])) return false;
    }
    return true;
    }

/**

  • toDouble:
  • تبدیل رشته s به double؛ در صورت ناتوانی در تبدیل، runtime_error پرتاب می‌کند.
    */
    static double toDouble(const string &s) {
    string str = s;
    for (auto &c : str) c = tolower(c); // make lowercase
double multiplier = 1.0;
text
if (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); }

}
/**

  • parseSINOffset:
  • اگر در خط ورودی، الگوی SIN(...) وجود داشته باشد، مقدار آفست (عدد اول داخل پرانتز) را استخراج می‌کند.
  • در غیر این صورت مقدار NaN برمی‌گرداند.
    */
    static double parseSINOffset(const string &line) {
    string lower = line;
    for (auto &c : lower) c = tolower(c);
    size_t pos = lower.find("sin(");
    if (pos == string::npos) {
    return nan("");
    }
    // یافتن پرانتز باز و بسته
    size_t start = line.find('(', pos);
    size_t end = line.find(')', start);
    if (start == string::npos || end == string::npos || end <= start + 1) {
    throw runtime_error("Syntax error: malformed SIN() in line: " + line);
    }
    // استخراج متن داخل پرانتز
    string inside = line.substr(start + 1, end - start - 1);
    // جایگزینی کاما با فاصله تا بتوانیم با istringstream بخوانیم
    for (auto &c : inside) {
    if (c == ',') c = ' ';
    }
    istringstream iss(inside);
    string first;
    if (!(iss >> first)) {
    throw runtime_error("Syntax error: empty SIN() arguments in line: " + line);
    }
    return toDouble(first);
    }

/**************************************************************************************************

  •                        NODE MAPPING (STRING → INTEGER)
    

**************************************************************************************************/
struct NodeMap {
unordered_map<string,int> name2idx;
vector<string> idx2name;

text
NodeMap() { // گره زمین با نام "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;

text
Type 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

text
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; } 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;

text
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; } 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;

text
if (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;

}

Thought

Evaluating test setup

Okay, so the user probably expects a transient sinusoidal simulation, though the code provided only handles DC constants. The test case might only print the DC value due to the limitations in how the voltage source is treated. To work around this, I’ll suggest a test netlist that defines a sinusoidal voltage source with both an offset and amplitude. I'll also include a resistor to ground and print the voltage across it over a full period using the .print TRAN command. I’ll explain how to run the code.

Providing a test netlist

I think the user needs a test case to verify the parsing and .print TRAN functionality. Based on the provided details, I'll create a .cir netlist with a sinusoidal voltage source and a resistor. The netlist will include the .print TRAN command, which will print voltage across the resistor over a complete period of a 50Hz frequency. Then, I'll provide instructions to run the test and show the expected output. I'll also clarify what the user can expect for simulation results, mainly a constant value due to the focus on DC offset.

Thought for 44 seconds

Answer

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
  • add V1 … SIN(0 5 50 0)
    • 0 V DC offset
    • 5 V amplitude
    • 50 Hz → period T = 0.02 s
    • 0° phase
  • add R1 … 1k pulls the source into a simple resistor‐to‐ground circuit
  • .print TRAN 0.001 0.02 0 0 V(1)
    • 1 ms time‐step → 20 points per period
    • run from t = 0 s to t = 0.02 s (one full cycle)
    • dump the node-1 voltage

Save that as, say, test.cir and run:

bash
./your_simulator_exe < test.cir

You should see something like:

text
Time 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.

Question

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

Thought

The user is working with a C++ circuit simulator and trying to create a test case involving a sinusoidal voltage source. The problem is that their output is showing zeros, which might be because the transient sinusoidal sources aren’t properly stamped. In the original code, only the DC components are treated with stamping, while transient sources like capacitors and inductors are only stamped during the simulation. They would need to implement the stampTransient method for their sinusoidal source if they want it to behave correctly in transient mode.

The current code only supports DC sources and incorrectly handles sinusoidal inputs. It treats the sinusoidal voltage source as a constant value using only the DC offset with no variation for amplitude or frequency. This leads to zero outputs in transient analysis, as seen in the user’s test case. To fix this, the VoltageSource class needs modification to handle sinusoidal sources during transient analysis by implementing a stampTransient method that accounts for amplitude, frequency, and phase to calculate the instantaneous voltage for each point in time.

To fix the zeros in the output, the code

Thought for 1m 14s

Answer

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:

cpp
add V1 1 0 DC 0.0

so in simulateTRAN it never actually injects a time-varying waveform—just a constant 0 V.


How to fix it

You need to:

  1. Parse all four SIN parameters (offset, amplitude, frequency, phase) and store them in your VoltageSource object.
  2. Override stampTransient for VoltageSource so that at each time step t you compute
    cpp
    double vnow = offset + amplitude * sin(2 * M_PI * frequency * t + phase);
    and then stamp that into your MNA right-hand side (just like you do in stamp for DC).

Step-by-step patch

  1. Extend the VoltageSource class (add fields and a stampTransient override):

    cpp
    class 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; } };
  2. Update your parseLine so that when you see SIN(off, amp, freq, phase) you:

    • extract all four numbers,
    • call new VoltageSource(...) with the DC‐placeholder offset, then
    • immediately do .setSin(offset, amplitude, frequency, phase) on that object instead of throwing away the AMP/FREQ/PHASE.
  3. Modify your simulateTRAN loop to pass the current time into stampTransient. Change:

    cpp
    e->stamp(A, b, nNode, vsMap); e->stampTransient(A, b, tstep, capVPrev, indIPrev, nNode, vsMap);

    into something like:

    cpp
    e->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

spice
add 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:

text
Time 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.

Question

#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;
}

text
cout << 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"
};

text
while (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; } }

}
/**

  • iequals:
  • مقایسهٔ دو رشته به صورت case-insensitive.
    */
    static bool iequals(const string &a, const string &b) {
    if (a.size() != b.size()) return false;
    for (size_t i = 0; i < a.size(); i++) {
    if (tolower(a[i]) != tolower(b[i])) return false;
    }
    return true;
    }

/**

  • istarts_with:
  • بررسی می‌کند آیا رشته s با prefix به صورت case-insensitive شروع می‌شود یا خیر.
    */
    static bool istarts_with(const string &s, const string &prefix) {
    if (s.size() < prefix.size()) return false;
    for (size_t i = 0; i < prefix.size(); i++) {
    if (tolower(s[i]) != tolower(prefix[i])) return false;
    }
    return true;
    }

/**

  • toDouble:
  • تبدیل رشته s به double؛ در صورت ناتوانی در تبدیل، runtime_error پرتاب می‌کند.
    */
    static double toDouble(const string &s) {
    string str = s;
    for (auto &c : str) c = tolower(c); // make lowercase
double multiplier = 1.0;
text
if (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); }

}
/**

  • parseSINOffset:
  • اگر در خط ورودی، الگوی SIN(...) وجود داشته باشد، مقدار آفست (عدد اول داخل پرانتز) را استخراج می‌کند.
  • در غیر این صورت مقدار NaN برمی‌گرداند.
    */
    static double parseSINOffset(const string &line) {
    string lower = line;
    for (auto &c : lower) c = tolower(c);
    size_t pos = lower.find("sin(");
    if (pos == string::npos) {
    return nan("");
    }
    // یافتن پرانتز باز و بسته
    size_t start = line.find('(', pos);
    size_t end = line.find(')', start);
    if (start == string::npos || end == string::npos || end <= start + 1) {
    throw runtime_error("Syntax error: malformed SIN() in line: " + line);
    }
    // استخراج متن داخل پرانتز
    string inside = line.substr(start + 1, end - start - 1);
    // جایگزینی کاما با فاصله تا بتوانیم با istringstream بخوانیم
    for (auto &c : inside) {
    if (c == ',') c = ' ';
    }
    istringstream iss(inside);
    string first;
    if (!(iss >> first)) {
    throw runtime_error("Syntax error: empty SIN() arguments in line: " + line);
    }
    return toDouble(first);
    }

/**************************************************************************************************

  •                        NODE MAPPING (STRING → INTEGER)
    

**************************************************************************************************/
struct NodeMap {
unordered_map<string,int> name2idx;
vector<string> idx2name;

text
NodeMap() { // گره زمین با نام "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;

text
Type 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

text
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; } 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;

text
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; } 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;

text
while(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

Thought

Reviewing user's code query

The user provided a circuit description and wants a transient simulation to run for one full period of a sine wave using a voltage source. The command's current parameters are missing key details like frequency and phase. In SPICE, the full syntax for a sine wave voltage source is SIN(Voff, Vamp, Freq, Delay, Damping, Phase). To produce a correct simulation, I need to calculate the proper simulation times (tstart, tstop, tstep) that span one full period of the sine wave, considering the missing details.

Providing simulation period

The user wants the simulation to cover one complete period of their sine wave voltage source. Since no frequency is specified, I’ll assume a default of 1 Hz, meaning the period would be 1 second. The .print Tran command would need to simulate this over one period, using a timestep of 0.01 seconds for sufficient resolution. The appropriate command would be ".print Tran 0.01 1.0 0 0 I(V1)" to provide data for one full period, with 100 points per period.

Thought for 39 seconds

Answer

To capture exactly one full cycle of your SIN source, you need to run the transient from t=0 to t=T, where

T=1fT = \frac{1}{f}

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:

  1. Assume a default frequency of 1 Hz, so T=1T=1 s.
  2. Explicitly give your SIN a frequency, e.g. 50 Hz, so T=1/50=0.02T=1/50=0.02 s.

1) If you want 1 Hz (period = 1 s)

spice
add 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.


2) If you want 50 Hz (period = 0.02 s)

spice
add 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 TT, and choose a time step small enough to give you the resolution you need (e.g. T/100T/100 for 100 points).

Question

i want to have the whole period

Thought

Clarifying the user request

The user wants a complete period for the command and is asking for clarity on the time range. They used SIN(1.0, 2.0), which doesn't specify frequency, so it's reasonable to assume a default frequency of 1 Hz. This implies the period is 1 second. The user mentioned they want the “whole period,” which I interpret as from t=0 to T. It looks like the current code doesn’t account for this assumption, so I need to correctly apply the default frequency and modify the simulation logic to match.

Thought for 10 seconds

Answer

To capture exactly one full cycle of your SIN source you need to:

  1. Specify the frequency in your SIN(…) so the simulator knows the period.
  2. Set your .print Tran to run from t=0 to T=1/f.

1) With a 1 Hz sine (period = 1 s)

spice
add 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.


2) With a 50 Hz sine (period ≈ 0.02 s)

spice
add 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

How it works

  • SIN(1.0,2.0,𝑓) sets offset = 1 V, amplitude = 2 V, frequency = 𝑓 Hz.
  • The period is T=1/fT=1/f.
  • .print Tran Δt T 0 0 I(V1) steps from t=0 to t=T in increments of Δt, dumping I(V1) each step.

Just choose the frequency you want, compute T=1/fT=1/f, and use that as the end‐time in your .print Tran.

Question

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

Thought

Identifying code issue

The user mentioned that the test case doesn’t show the full period for the sinusoidal signal, and I think they’re pointing out an issue with how the simulation processes the SIN function. It seems like the

Thought for 8 seconds

Answer

To get a real sine-wave into your transient and see a full cycle, you must:

  1. Parse all of SIN’s parameters (offset, amplitude, frequency, phase), not just the offset.
  2. Store them inside your VoltageSource object.
  3. Override stampTransient in VoltageSource so it computes v(t)=offset+amplitudesin(2πfrequencyt+phase) v(t) = \text{offset} + \text{amplitude}\,\sin\bigl(2\pi\,\text{frequency}\,t + \text{phase}\bigr) at each time step, and stamps that value into your MNA RHS.
  4. Pass the current time t into every call to stampTransient.

1) Extend VoltageSource

Replace your current VoltageSource with something like:

cpp
class 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; } };

2) Parse all four SIN args in 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:

cpp
tuple<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 }

3) Propagate time into every stampTransient

Change your loop from

cpp
for (const auto& e : elements) { e->stamp(A,b,nNode,vsMap); e->stampTransient(A,b,dt,capVPrev,indIPrev,nNode,vsMap); }

to

cpp
for (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).


4) Test with one-period netlist

Now your driver will correctly produce a sine. For example, for a 1 Hz source:

spice
add 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:

text
Time 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.

Question

#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
    

************************************************************************************************/
/

  • tokenize:
  • ورودی خط را بر حسب فضاهای خالی تقسیم می‌کند و لیست کلمات حاصل را بازمی‌گرداند.
    */
    // 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;
    }

/**

  • iequals:
  • مقایسهٔ دو رشته به صورت case-insensitive.
    */
    static bool iequals(const string &a, const string &b) {
    if (a.size() != b.size()) return false;
    for (size_t i = 0; i < a.size(); i++) {
    if (tolower(a[i]) != tolower(b[i])) return false;
    }
    return true;
    }

/**

  • istarts_with:
  • بررسی می‌کند آیا رشته s با prefix به صورت case-insensitive شروع می‌شود یا خیر.
    */
    static bool istarts_with(const string &s, const string &prefix) {
    if (s.size() < prefix.size()) return false;
    for (size_t i = 0; i < prefix.size(); i++) {
    if (tolower(s[i]) != tolower(prefix[i])) return false;
    }
    return true;
    }

/**

  • toDouble:
  • تبدیل رشته s به double؛ در صورت ناتوانی در تبدیل، runtime_error پرتاب می‌کند.
    */
    static double toDouble(const string &s) {
    string str = s;
    for (auto &c : str) c = tolower(c); // make lowercase
double multiplier = 1.0;
text
if (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); }

}
/**

  • parseSINOffset:
  • اگر در خط ورودی، الگوی SIN(...) وجود داشته باشد، مقدار آفست (عدد اول داخل پرانتز) را استخراج می‌کند.
  • در غیر این صورت مقدار NaN برمی‌گرداند.
    */
    static double parseSINOffset(const string &line) {
    string lower = line;
    for (auto &c : lower) c = tolower(c);
    size_t pos = lower.find("sin(");
    if (pos == string::npos) {
    return nan("");
    }
    // یافتن پرانتز باز و بسته
    size_t start = line.find('(', pos);
    size_t end = line.find(')', start);
    if (start == string::npos || end == string::npos || end <= start + 1) {
    throw runtime_error("Syntax error: malformed SIN() in line: " + line);
    }
    // استخراج متن داخل پرانتز
    string inside = line.substr(start + 1, end - start - 1);
    // جایگزینی کاما با فاصله تا بتوانیم با istringstream بخوانیم
    for (auto &c : inside) {
    if (c == ',') c = ' ';
    }
    istringstream iss(inside);
    string first;
    if (!(iss >> first)) {
    throw runtime_error("Syntax error: empty SIN() arguments in line: " + line);
    }
    return toDouble(first);
    }

/**************************************************************************************************

  •                        NODE MAPPING (STRING → INTEGER)
    

**************************************************************************************************/
struct NodeMap {
unordered_map<string,int> name2idx;
vector<string> idx2name;

text
NodeMap() { // گره زمین با نام "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;

text
Type 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

text
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; } 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;

text
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; } 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;

text
if (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

Thought

Adapting code for transient printing

The user wants to modify their code to support transient printing using the ".print TRAN" command. This involves adapting the code to handle this print functionality specifically for transient responses. I think they'll need to implement a proper parsing function for ".print TRAN" and have it trigger the correct printing behavior when the mode is set to TRAN. The next step, I believe, is implementing support for transient printing in the solveDC section where pr.mode == TRAN.

Fixing transient print

The user wants to modify their code to handle ".print TRAN" for transient printing. The existing code has a simulateTRAN function that doesn’t correctly use the tstart parameter. The signature for simulateTRAN has tstart as an argument, but currently, pr.startVal is mapped to tstart incorrectly. They also discard tmax, missing a crucial step. To fix this, I need to update the call to `

Thought for 31 seconds

Answer

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:

spice
add 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
  1. SIN line: SIN(1.0,2.0,1.0) → T = 1/1.0 Hz = 1 s
  2. .print TRAN:
    • 0.001 s step → 1000 points per cycle
    • 1.0 s stop → one full period
    • 0.0 0.0 → start time = 0, tmax ignored
    • I(V1) → the current through V1

When you feed that into your program (via stdin or file), you’ll get output like:

text
Time 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.

Share this Q&A