1 //**************************************************************************************************
2 // PrcNgSpice.cpp *
3 // ---------------- *
4 // Started : 2004-05-07 *
5 // Last Update : 2015-03-20 *
6 // Copyright : (C) 2004 by MSWaters *
7 //**************************************************************************************************
8
9 //**************************************************************************************************
10 // *
11 // This program is free software; you can redistribute it and/or modify it under the *
12 // terms of the GNU General Public License as published by the Free Software Foundation; *
13 // either version 3 of the License, or (at your option) any later version. *
14 // *
15 //**************************************************************************************************
16
17 #include "PrcNgSpice.hpp"
18
19 //**************************************************************************************************
20 // Constructor.
21
PrcNgSpice(void)22 PrcNgSpice::PrcNgSpice( void ) : PrcSimEngBase( )
23 {
24 // Set the simulation engine type
25 m_eSimEng = eSIMR_NGSPICE;
26
27 // Set the simulator binary file name if it can be found
28 bSetBinary( BIN_NGSPICE );
29 }
30
31 //**************************************************************************************************
32 // Destructor.
33
~PrcNgSpice()34 PrcNgSpice::~PrcNgSpice( )
35 {
36 }
37
38 //**************************************************************************************************
39 // Match a component to a set of node labels.
40 //
41 // Component voltages cannot be specified using the component label in the NG-Spice print statement.
42 // The two node labels connected to the component must be specified. When parsing the print
43 // statement the component label must be derived from the node labels. This may not work where
44 // components are connected in parallel. The nodes are specified in the form "<Node1>,<Node2>"
45 // eg. "1,2".
46 //
47 // Argument List :
48 // roSimn - The simulation object
49 // rosToNodes - The nodes to match, also used to return the component label
50 //
51 // Return Values :
52 // true - Success (a component with the specified nodes was found)
53 // false - Failure
54
bMatchCpnt(SimnNgSpice & roSimn,wxString & rosToNodes)55 bool PrcNgSpice::bMatchCpnt( SimnNgSpice & roSimn, wxString & rosToNodes )
56 {
57 wxArrayString osaNodes;
58 Component tCpnt;
59 wxString os1;
60 size_t sz1;
61
62 // Argument validity checks
63 if( rosToNodes.IsEmpty( ) ) return( false );
64 if( roSimn.m_oaCpnts.GetCount( ) <= 0 ) return( false );
65 if( rosToNodes.Freq( wxT(',') ) != 1 ) return( false );
66
67 // Extract the node labels
68 osaNodes.Add( rosToNodes.BeforeFirst( wxT(',') ) );
69 osaNodes.Add( rosToNodes.AfterLast( wxT(',') ) );
70
71 // Attempt to match the nodes with a component
72 for( sz1=0; sz1<roSimn.m_oaCpnts.GetCount( ); sz1++ )
73 {
74 tCpnt = roSimn.m_oaCpnts.Item( sz1 );
75 if( ! tCpnt.bParse( ) ) continue;
76 if( tCpnt.rosaGetNodes( ) == osaNodes ) break;
77 }
78
79 // Was a match found?
80 if( ! tCpnt.bIsValid( ) ) return( false );
81
82 rosToNodes = tCpnt.rosGetName( );
83
84 return( true );
85 }
86
87 //**************************************************************************************************
88 // Convert a list of component labels to the corresponding list of node pairs.
89 //
90 // Argument List :
91 // rosaCpnts - A list of component labels
92 // roSimn - A simulation object
93
Convert2Nodes(wxArrayString & rosaCpnts,SimnNgSpice & roSimn)94 void PrcNgSpice::Convert2Nodes( wxArrayString & rosaCpnts, SimnNgSpice & roSimn )
95 {
96 Component tCpnt;
97 wxString os1;
98 size_t sz1;
99
100 for( sz1=0; sz1<rosaCpnts.GetCount( ); sz1++ )
101 {
102 os1 = rosaCpnts.Item( sz1 );
103 tCpnt = roSimn.roGetCpnt( os1 );
104 if( ! tCpnt.bParse( ) ) continue;
105 if( tCpnt.rosaGetNodes( ).GetCount( ) != 2 ) continue;
106 os1.Empty( );
107 os1 << tCpnt.rosaGetNodes( ).Item( 0 ) << wxT(',') << tCpnt.rosaGetNodes( ).Item( 1 );
108 rosaCpnts.Item( sz1 ) = os1;
109 }
110 }
111
112 //**************************************************************************************************
113 // Create a WIDTH command based on a PRINT command.
114 //
115 // By default raw output from NG-Spice uses an 80 column output width which allows about two
116 // dependant variables to be displayed. To display more information a .WIDTH statement must be added
117 // to increase the number columns in the output.
118 //
119 // Argument List :
120 // rosCmdPR - The Print command
121 //
122 // Return Values :
123 // Success - A .WIDTH command
124 // Failure - An empty string
125
rosMakeCmdWIDTH(wxString & rosCmdPR)126 wxString & PrcNgSpice::rosMakeCmdWIDTH( wxString & rosCmdPR )
127 {
128 static wxString osCmdWIDTH;
129 wxStringTokenizer ostk1;
130 wxString os1;
131 int i1=0;
132
133 osCmdWIDTH.Empty( );
134
135 ostk1 = rosCmdPR;
136 if( ostk1.GetNextToken( ).StartsWith( wxT(".PR") ) )
137 {
138 os1 = ostk1.GetNextToken( );
139
140 if( os1.StartsWith( wxT("DC") ) )
141 i1 = 8 + 16 + ostk1.CountTokens( ) * 16;
142 else if( os1.StartsWith( wxT("AC") ) )
143 i1 = 8 + 2 * 16 + ostk1.CountTokens( ) * 16;
144 else if( os1.StartsWith( wxT("TR") ) )
145 i1 = 8 + 16 + ostk1.CountTokens( ) * 16;
146
147 if( i1 > 80 ) osCmdWIDTH << wxT(".WIDTH OUT=") << i1;
148 }
149
150 return( osCmdWIDTH );
151 }
152
153 //**************************************************************************************************
154 // This function is a generic results file formatter which works in most circumstance.
155 //
156 // Return Values :
157 // true - Success
158 // false - Failure
159
bFmtCommon(void)160 bool PrcNgSpice::bFmtCommon( void )
161 {
162 wxString os1;
163 size_t sz1, sz2;
164
165 // This function requires the results file to be open
166 if( ! m_oFileResults.IsOpened( ) ) return( false );
167
168 // Find the beginning of the data area (ie. the 1st line starting with "Index")
169 for( sz1=0; sz1<m_oFileResults.GetLineCount( ); sz1++ )
170 {
171 os1 = m_oFileResults.GetLine( sz1 );
172 if( os1.StartsWith( wxT("Index") ) ) break;
173 }
174 if( sz1 >= (m_oFileResults.GetLineCount( )-1) )
175 {
176 SetErrMsg( wxT("Couldn't find the data section in the results file.") );
177 return( false );
178 }
179
180 // Delete everything before the data area
181 sz2 = sz1;
182 for( sz1=0; sz1<sz2; sz1++ ) m_oFileResults.RemoveLine( 0 );
183 if( m_oFileResults.GetLineCount( ) > 0 ) m_oFileResults.RemoveLine( 1 );
184
185 // Format the column header line
186 bFmtColLabels( );
187
188 // Delete lines other than data lines
189 for( sz1=1; sz1<m_oFileResults.GetLineCount( ); sz1++ )
190 {
191 os1 = m_oFileResults.GetLine( sz1 );
192 if( ! wxIsdigit( os1.GetChar( 0 ) ) )
193 {
194 m_oFileResults.RemoveLine( sz1 );
195 sz1--;
196 }
197 }
198
199 // Format the data lines
200 bFmtDataLines( );
201
202 return( true );
203 }
204
205 //**************************************************************************************************
206 // Format the column header in the results file.
207 //
208 // Return Values :
209 // true - Success
210 // false - Failure
211
bFmtColLabels(void)212 bool PrcNgSpice::bFmtColLabels( void )
213 {
214 wxStringTokenizer ostk1;
215 wxString osLine, os1;
216 int i1;
217
218 // This function requires the results file to be open
219 if( ! m_oFileResults.IsOpened( ) ) return( false );
220
221 // Get the column header line
222 ostk1 = m_oFileResults.GetFirstLine( );
223 if( ostk1.CountTokens( ) < 3 ) return( false );
224
225 // Dispose of the first field (ie. "Index")
226 ostk1.GetNextToken( );
227
228 // Extract the operator field and replace the NG-Spice column labels
229 os1 << ostk1.GetNextToken( ) << wxT(' ') << m_osColLbls;
230 ostk1.SetString( os1 );
231
232 // Format each field
233 // (06/08/2005) gWave breaks if there's a space after initial '#' ???
234 // osLine = wxT("# ");
235 osLine = wxT('#');
236 while( ostk1.HasMoreTokens( ) )
237 {
238 if( osLine.Length( ) > 2 )
239 { // Pad the column with spaces to the required width
240 i1 = NGSPICE_COL_WD - (osLine.Length( ) % NGSPICE_COL_WD) + 1;
241 if( i1 >= NGSPICE_COL_WD ) i1 = 1;
242 osLine.Append( wxT(' '), i1 );
243 }
244
245 // Add the next label to the line
246 osLine << ostk1.GetNextToken( );
247 }
248
249 osLine.Trim( ); // Remove trailing space characters
250
251 m_oFileResults.GetFirstLine( ) = osLine;
252
253 return( true );
254 }
255
256 //**************************************************************************************************
257 // Format a data lines in the results file.
258 //
259 // Return Values :
260 // true - Success
261 // false - Failure
262
bFmtDataLines(void)263 bool PrcNgSpice::bFmtDataLines( void )
264 {
265 wxStringTokenizer ostk1;
266 wxString osLine, os1;
267 size_t sz1;
268 double df1;
269 int i1;
270
271 // This function requires the results file to be open
272 if( ! m_oFileResults.IsOpened( ) ) return( false );
273
274 for( sz1=1; sz1<m_oFileResults.GetLineCount( ); sz1++ )
275 {
276 // Get a line of data
277 osLine = m_oFileResults.GetLine( sz1 );
278
279 // Tokenize the string
280 ostk1.SetString( osLine );
281 if( ostk1.CountTokens( ) < 3 ) return( false );
282
283 // Dispose of the first field
284 ostk1.GetNextToken( );
285
286 // Format each field
287 osLine.Empty( );
288 while( ostk1.HasMoreTokens( ) )
289 {
290 // Add the next parameter to the line
291 os1 = ostk1.GetNextToken( );
292 if( os1.Last( ) == wxT(',') )
293 {
294 os1.Last( ) = wxT(' ');
295 ostk1.GetNextToken( );
296 }
297 if( ! CnvtType::bStrToFlt( os1, &df1 ) ) df1 = -9.999e+99;
298 if( ! CnvtType::bFltToStr( df1, os1 ) ) os1 = wxT("-9.999e+99");
299 osLine << os1;
300
301 // Pad the column with spaces to the required width
302 i1 = NGSPICE_COL_WD - (osLine.Length( ) % NGSPICE_COL_WD);
303 osLine.Append( wxT(' '), i1 );
304 }
305
306 m_oFileResults.GetLine( sz1 ) = osLine;
307
308 osLine.Trim( ); // Remove trailing space characters
309 }
310
311 return( true );
312 }
313
314 //**************************************************************************************************
315 // Format any phase values contained in the results file.
316 //
317 // This is only pertinent for an AC analysis. It involves removing the discontinuity at 360 degree
318 // in the data.
319 //
320 // Return Values :
321 // true - Success
322 // false - Failure
323
bFmtPhaseData(void)324 bool PrcNgSpice::bFmtPhaseData( void )
325 {
326 // ??? (02/02/2006) Not yet implemented
327
328 return( true );
329 }
330
331 //**************************************************************************************************
332 // Make the process argument list based on the contents of a simulation object.
333 //
334 // Argument List :
335 // roSimn - The simulation object
336 //
337 // Return Values :
338 // true - Success
339 // false - Failure
340
bMakeArgLst(SimnBase & roSimn)341 bool PrcNgSpice::bMakeArgLst( SimnBase & roSimn )
342 {
343 wxString os1;
344
345 // Envoke the base class operations
346 if( ! PrcSimEngBase::bMakeArgLst( roSimn ) ) return( false );
347
348 // Construct the command line to execute the simulation
349 os1 = wxT("-n -b ") + roSimn.roGetSaveFile( ).GetFullPath( );
350 if( ! PrcBase::bSetArgLst( os1 ) )
351 {
352 SetErrMsg( wxT("Couldn't set argument list") );
353 return( false );
354 }
355
356 // Get results file column labels (replace node pairs with component names)
357 if( roSimn.eGetSimEng( ) == eSIMR_NGSPICE )
358 m_osColLbls = ((SimnNgSpice &) roSimn).m_oCmdPR.rosGetParamLst( );
359
360 return( true );
361 }
362
363 //**************************************************************************************************
364 // Format the contents of the results file so that gWave can read it.
365 //
366 // Ie. add a header line at the top containing parameter names followed by the columns of data.
367 //
368 // Return Values :
369 // true - Success
370 // false - Failure
371
bFmtResults(void)372 bool PrcNgSpice::bFmtResults( void )
373 {
374 bool bRtn;
375 wxString os1;
376
377 m_osErrMsg.Empty( );
378
379 // Check that the results file exists
380 if( ! roGetResultsFile( ).FileExists( ) )
381 {
382 os1.Empty( );
383 os1 << wxT("Results file doesn't exist : \n\n")
384 << roGetResultsFile( ).GetFullPath( );
385 SetErrMsg( os1 );
386 return( false );
387 }
388
389 // Attempt to open the results file
390 if( ! m_oFileResults.Open( roGetResultsFile( ).GetFullPath( ) ) )
391 {
392 os1.Empty( );
393 os1 << wxT("Results file couldn't be opened : \n\n")
394 << roGetResultsFile( ).GetFullPath( );
395 SetErrMsg( os1 );
396 return( false );
397 }
398
399 // Format the simulation results
400 bRtn = bFmtCommon( );
401
402 // Need last line to end with a '\n' or text controls will not load it
403 os1 = m_oFileResults.GetLastLine( );
404 if( ! os1.IsEmpty( ) )
405 if( os1.Last( ) != wxT('\n') )
406 m_oFileResults.GetLastLine( ) << wxT('\n');
407
408 m_oFileResults.Write( ); // Save the changes to disk
409 m_oFileResults.Close( ); // Close the file
410
411 return( bRtn );
412 }
413
414 //**************************************************************************************************
415