Enhance sensitivity analysis with an option to choose the parameters
to be varied. Shell-style wildcards ("*?") are supported.
This commit is contained in:
parent
c30bc423ba
commit
abc3fceb7e
|
|
@ -18,15 +18,11 @@ Modified: 2000 AlanFixes
|
||||||
/* #define ASDEBUG */
|
/* #define ASDEBUG */
|
||||||
#ifdef ASDEBUG
|
#ifdef ASDEBUG
|
||||||
#define DEBUG(X) if ((X) < Sens_Debug)
|
#define DEBUG(X) if ((X) < Sens_Debug)
|
||||||
int Sens_Debug = 0;
|
static int Sens_Debug = 0;
|
||||||
char SF1[] = "res";
|
|
||||||
char SF2[] = "dc";
|
|
||||||
char SF3[] = "bf";
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
char* Sfilter = NULL;
|
static double Sens_Delta = 0.000001;
|
||||||
double Sens_Delta = 0.000001;
|
static double Sens_Abs_Delta = 0.000001;
|
||||||
double Sens_Abs_Delta = 0.000001;
|
|
||||||
|
|
||||||
static int sens_setp(sgen* sg, CKTcircuit* ckt, IFvalue* val);
|
static int sens_setp(sgen* sg, CKTcircuit* ckt, IFvalue* val);
|
||||||
static int sens_load(sgen* sg, CKTcircuit* ckt, int is_dc);
|
static int sens_load(sgen* sg, CKTcircuit* ckt, int is_dc);
|
||||||
|
|
@ -34,6 +30,42 @@ static int sens_temp(sgen* sg, CKTcircuit* ckt);
|
||||||
static int count_steps(int type, double low, double high, int steps, double* stepsize);
|
static int count_steps(int type, double low, double high, int steps, double* stepsize);
|
||||||
static double inc_freq(double freq, int type, double step_size);
|
static double inc_freq(double freq, int type, double step_size);
|
||||||
|
|
||||||
|
char **Sens_filter;
|
||||||
|
|
||||||
|
/* Match a potential vector name against a filter string. */
|
||||||
|
|
||||||
|
static int scan(char *filter, char *name)
|
||||||
|
{
|
||||||
|
while (*filter && *name) {
|
||||||
|
if (*filter == '*') {
|
||||||
|
if (!filter[1])
|
||||||
|
return 1; // Terminal '*' matches all.
|
||||||
|
while (*name && !scan(filter + 1, name))
|
||||||
|
++name;
|
||||||
|
return *name != '\0';
|
||||||
|
}
|
||||||
|
if (*filter == *name || *filter == '?')
|
||||||
|
++name, ++filter;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match if both strings exhausted. */
|
||||||
|
|
||||||
|
return !*name && (!*filter || (*filter == '*' && !filter[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int check_filter(char *name)
|
||||||
|
{
|
||||||
|
char **pp, *filter;
|
||||||
|
|
||||||
|
for (pp = Sens_filter; (filter = *pp); pp++) {
|
||||||
|
if (scan(filter, name))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#define save_context(thing, place) { \
|
#define save_context(thing, place) { \
|
||||||
place = thing; \
|
place = thing; \
|
||||||
}
|
}
|
||||||
|
|
@ -92,8 +124,7 @@ int sens_sens(CKTcircuit* ckt, int restart)
|
||||||
int k, j, n;
|
int k, j, n;
|
||||||
int num_vars, branch_eq = 0;
|
int num_vars, branch_eq = 0;
|
||||||
runDesc* sen_data = NULL;
|
runDesc* sen_data = NULL;
|
||||||
char namebuf[513];
|
IFuid *vec_names, *output_names, freq_name;
|
||||||
IFuid* output_names, freq_name;
|
|
||||||
int bypass;
|
int bypass;
|
||||||
int type;
|
int type;
|
||||||
double* saved_rhs = NULL,
|
double* saved_rhs = NULL,
|
||||||
|
|
@ -185,31 +216,53 @@ int sens_sens(CKTcircuit* ckt, int restart)
|
||||||
if (!num_vars)
|
if (!num_vars)
|
||||||
return OK; /* XXXX Should be E_ something */
|
return OK; /* XXXX Should be E_ something */
|
||||||
|
|
||||||
k = 0;
|
|
||||||
output_names = TMALLOC(IFuid, num_vars);
|
output_names = TMALLOC(IFuid, num_vars);
|
||||||
|
k = 0;
|
||||||
|
num_vars = 0;
|
||||||
for (sg = sgen_init(ckt, is_dc); sg; sgen_next(&sg)) {
|
for (sg = sgen_init(ckt, is_dc); sg; sgen_next(&sg)) {
|
||||||
|
char namebuf[513];
|
||||||
|
|
||||||
if (!sg->is_instparam) {
|
if (!sg->is_instparam) {
|
||||||
sprintf(namebuf, "%s:%s",
|
snprintf(namebuf, sizeof namebuf, "%s:%s",
|
||||||
sg->instance->GENname,
|
sg->instance->GENname,
|
||||||
sg->ptable[sg->param].keyword);
|
sg->ptable[sg->param].keyword);
|
||||||
}
|
}
|
||||||
else if ((sg->ptable[sg->param].dataType
|
else if ((sg->ptable[sg->param].dataType
|
||||||
& IF_PRINCIPAL) && sg->is_principle == 1)
|
& IF_PRINCIPAL) && sg->is_principle == 1)
|
||||||
{
|
{
|
||||||
sprintf(namebuf, "%s", sg->instance->GENname);
|
snprintf(namebuf, sizeof namebuf, "%s", sg->instance->GENname);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sprintf(namebuf, "%s_%s",
|
snprintf(namebuf, sizeof namebuf, "%s_%s",
|
||||||
sg->instance->GENname,
|
sg->instance->GENname,
|
||||||
sg->ptable[sg->param].keyword);
|
sg->ptable[sg->param].keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
SPfrontEnd->IFnewUid(ckt,
|
/* Check against the filter list. */
|
||||||
output_names + k, NULL,
|
|
||||||
|
if (!Sens_filter || check_filter(namebuf)) {
|
||||||
|
num_vars++;
|
||||||
|
SPfrontEnd->IFnewUid(ckt, output_names + k, NULL,
|
||||||
namebuf, UID_OTHER, NULL);
|
namebuf, UID_OTHER, NULL);
|
||||||
|
}
|
||||||
k += 1;
|
k += 1;
|
||||||
}
|
}
|
||||||
|
if (num_vars == k) {
|
||||||
|
vec_names = output_names;
|
||||||
|
} else {
|
||||||
|
if (!num_vars) {
|
||||||
|
FREE(output_names);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make a non-sparse array of names for OUTpBeginPlot(). */
|
||||||
|
|
||||||
|
vec_names = TMALLOC(IFuid, num_vars);
|
||||||
|
for (i = 0, j = 0; j < num_vars; ++i) {
|
||||||
|
if (output_names[i])
|
||||||
|
vec_names[j++] = output_names[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (is_dc) {
|
if (is_dc) {
|
||||||
type = IF_REAL;
|
type = IF_REAL;
|
||||||
freq_name = NULL;
|
freq_name = NULL;
|
||||||
|
|
@ -221,15 +274,18 @@ int sens_sens(CKTcircuit* ckt, int restart)
|
||||||
"frequency", UID_OTHER, NULL);
|
"frequency", UID_OTHER, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
error = SPfrontEnd->OUTpBeginPlot(
|
error = SPfrontEnd->OUTpBeginPlot(ckt, ckt->CKTcurJob,
|
||||||
ckt, ckt->CKTcurJob,
|
|
||||||
ckt->CKTcurJob->JOBname,
|
ckt->CKTcurJob->JOBname,
|
||||||
freq_name, IF_REAL,
|
freq_name, IF_REAL,
|
||||||
num_vars, output_names, type, &sen_data);
|
num_vars, vec_names,
|
||||||
if (error)
|
type, &sen_data);
|
||||||
return error;
|
if (vec_names != output_names)
|
||||||
|
FREE(vec_names);
|
||||||
|
if (error) {
|
||||||
|
err:
|
||||||
FREE(output_names);
|
FREE(output_names);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
if (is_dc) {
|
if (is_dc) {
|
||||||
output_values = TMALLOC(double, num_vars);
|
output_values = TMALLOC(double, num_vars);
|
||||||
output_cvalues = NULL;
|
output_cvalues = NULL;
|
||||||
|
|
@ -305,7 +361,8 @@ int sens_sens(CKTcircuit* ckt, int restart)
|
||||||
|
|
||||||
if (SPfrontEnd->IFpauseTest()) {
|
if (SPfrontEnd->IFpauseTest()) {
|
||||||
/* XXX Save State */
|
/* XXX Save State */
|
||||||
return E_PAUSE;
|
error = E_PAUSE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (j = 0; j < size; j++) {
|
for (j = 0; j < size; j++) {
|
||||||
|
|
@ -322,13 +379,13 @@ int sens_sens(CKTcircuit* ckt, int restart)
|
||||||
/* XXX Free old states */
|
/* XXX Free old states */
|
||||||
error = CKTunsetup(ckt);
|
error = CKTunsetup(ckt);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
goto err;
|
||||||
|
|
||||||
/* XXX ckt->CKTmatrix->SPmatrix = Y; */
|
/* XXX ckt->CKTmatrix->SPmatrix = Y; */
|
||||||
|
|
||||||
error = CKTsetup(ckt);
|
error = CKTsetup(ckt);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
goto err;
|
||||||
|
|
||||||
#ifdef notdef
|
#ifdef notdef
|
||||||
for (j = 0; j <= ckt->CKTmaxOrder + 1; j++) {
|
for (j = 0; j <= ckt->CKTmaxOrder + 1; j++) {
|
||||||
|
|
@ -338,10 +395,10 @@ int sens_sens(CKTcircuit* ckt, int restart)
|
||||||
#endif
|
#endif
|
||||||
error = CKTtemp(ckt);
|
error = CKTtemp(ckt);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
goto err;
|
||||||
error = CKTload(ckt); /* INITSMSIGS */
|
error = CKTload(ckt); /* INITSMSIGS */
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
goto err;
|
||||||
|
|
||||||
#ifdef KLU
|
#ifdef KLU
|
||||||
if (ckt->CKTmatrix->CKTkluMODE)
|
if (ckt->CKTmatrix->CKTkluMODE)
|
||||||
|
|
@ -363,7 +420,7 @@ int sens_sens(CKTcircuit* ckt, int restart)
|
||||||
|
|
||||||
error = NIacIter(ckt);
|
error = NIacIter(ckt);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
goto err;
|
||||||
|
|
||||||
#ifdef notdef
|
#ifdef notdef
|
||||||
/* XXX Why? */
|
/* XXX Why? */
|
||||||
|
|
@ -389,10 +446,13 @@ int sens_sens(CKTcircuit* ckt, int restart)
|
||||||
ckt->CKTmatrix = delta_Y;
|
ckt->CKTmatrix = delta_Y;
|
||||||
|
|
||||||
/* calc. effect of each param */
|
/* calc. effect of each param */
|
||||||
|
|
||||||
|
k = 0;
|
||||||
for (sg = sgen_init(ckt, is_dc /* job->plist */);
|
for (sg = sgen_init(ckt, is_dc /* job->plist */);
|
||||||
sg; sgen_next(&sg))
|
sg; sgen_next(&sg))
|
||||||
{
|
{
|
||||||
|
if (!output_names[k++])
|
||||||
|
continue; // Ignore filtered parameters.
|
||||||
#ifdef ASDEBUG
|
#ifdef ASDEBUG
|
||||||
DEBUG(2) {
|
DEBUG(2) {
|
||||||
printf("E/iE: %x/%x; delta_I/iI: %x/%x\n",
|
printf("E/iE: %x/%x; delta_I/iI: %x/%x\n",
|
||||||
|
|
@ -486,7 +546,7 @@ int sens_sens(CKTcircuit* ckt, int restart)
|
||||||
#endif
|
#endif
|
||||||
if (sens_load(sg, ckt, is_dc)) {
|
if (sens_load(sg, ckt, is_dc)) {
|
||||||
if (error && error != E_BADPARM)
|
if (error && error != E_BADPARM)
|
||||||
return error; /* XXX */
|
goto err;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -521,7 +581,7 @@ int sens_sens(CKTcircuit* ckt, int restart)
|
||||||
|
|
||||||
sens_setp(sg, ckt, &nvalue);
|
sens_setp(sg, ckt, &nvalue);
|
||||||
if (error && error != E_BADPARM)
|
if (error && error != E_BADPARM)
|
||||||
return error;
|
goto err;
|
||||||
|
|
||||||
SMPconstMult(delta_Y, -1.0);
|
SMPconstMult(delta_Y, -1.0);
|
||||||
|
|
||||||
|
|
@ -709,7 +769,6 @@ int sens_sens(CKTcircuit* ckt, int restart)
|
||||||
|
|
||||||
release_context(ckt->CKTrhs, saved_rhs);
|
release_context(ckt->CKTrhs, saved_rhs);
|
||||||
release_context(ckt->CKTirhs, saved_irhs);
|
release_context(ckt->CKTirhs, saved_irhs);
|
||||||
|
|
||||||
release_context(ckt->CKTmatrix, saved_matrix);
|
release_context(ckt->CKTmatrix, saved_matrix);
|
||||||
|
|
||||||
if (is_dc)
|
if (is_dc)
|
||||||
|
|
@ -724,6 +783,7 @@ int sens_sens(CKTcircuit* ckt, int restart)
|
||||||
freq = inc_freq(freq, job->step_type, step_size);
|
freq = inc_freq(freq, job->step_type, step_size);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
FREE(output_names);
|
||||||
|
|
||||||
SPfrontEnd->OUTendPlot(sen_data);
|
SPfrontEnd->OUTendPlot(sen_data);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,10 @@ Copyright 1991 Regents of the University of California. All rights reserved.
|
||||||
#include "ngspice/ifsim.h"
|
#include "ngspice/ifsim.h"
|
||||||
#include "ngspice/sensgen.h"
|
#include "ngspice/sensgen.h"
|
||||||
|
|
||||||
/* Used by sensitivity code in cktsens.c */
|
static int set_model(sgen *);
|
||||||
|
static int set_param(sgen *);
|
||||||
extern char *Sfilter;
|
static int set_inst(sgen *);
|
||||||
|
static int set_dev(sgen *);
|
||||||
int set_model(sgen *);
|
|
||||||
int set_param(sgen *);
|
|
||||||
int set_inst(sgen *);
|
|
||||||
int set_dev(sgen *);
|
|
||||||
|
|
||||||
sgen *
|
sgen *
|
||||||
sgen_init(CKTcircuit *ckt, int is_dc)
|
sgen_init(CKTcircuit *ckt, int is_dc)
|
||||||
|
|
@ -176,33 +172,30 @@ sgen_next(sgen **xsg)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_inst(sgen *sg)
|
static int set_inst(sgen *sg)
|
||||||
{
|
{
|
||||||
NG_IGNORE(sg);
|
NG_IGNORE(sg);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_model(sgen *sg)
|
static int set_model(sgen *sg)
|
||||||
{
|
{
|
||||||
NG_IGNORE(sg);
|
NG_IGNORE(sg);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_dev(sgen *sg)
|
static int set_dev(sgen *sg)
|
||||||
{
|
{
|
||||||
NG_IGNORE(sg);
|
NG_IGNORE(sg);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_param(sgen *sg)
|
static int set_param(sgen *sg)
|
||||||
{
|
{
|
||||||
IFvalue ifval;
|
IFvalue ifval;
|
||||||
|
|
||||||
if (!sg->ptable[sg->param].keyword)
|
if (!sg->ptable[sg->param].keyword)
|
||||||
return 0;
|
return 0;
|
||||||
if (Sfilter && strncmp(sg->ptable[sg->param].keyword, Sfilter,
|
|
||||||
strlen(Sfilter)))
|
|
||||||
return 0;
|
|
||||||
if ((sg->ptable[sg->param].dataType &
|
if ((sg->ptable[sg->param].dataType &
|
||||||
(IF_SET|IF_ASK|IF_REAL|IF_VECTOR|IF_REDUNDANT|IF_NONSENSE))
|
(IF_SET|IF_ASK|IF_REAL|IF_VECTOR|IF_REDUNDANT|IF_NONSENSE))
|
||||||
!= (IF_SET|IF_ASK|IF_REAL))
|
!= (IF_SET|IF_ASK|IF_REAL))
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ Modified: 2000 AlansFixes
|
||||||
#include "ngspice/ngspice.h"
|
#include "ngspice/ngspice.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "ngspice/ifsim.h"
|
#include "ngspice/ifsim.h"
|
||||||
|
#include "ngspice/iferrmsg.h"
|
||||||
#include "ngspice/inpdefs.h"
|
#include "ngspice/inpdefs.h"
|
||||||
#include "ngspice/inpmacs.h"
|
#include "ngspice/inpmacs.h"
|
||||||
#include "ngspice/fteext.h"
|
#include "ngspice/fteext.h"
|
||||||
|
|
@ -438,6 +439,7 @@ dot_sens(char *line, CKTcircuit *ckt, INPtables *tab, struct card *current,
|
||||||
{
|
{
|
||||||
char *name; /* the resistor's name */
|
char *name; /* the resistor's name */
|
||||||
int error; /* error code temporary */
|
int error; /* error code temporary */
|
||||||
|
int filters, fidx; /* Filter allocation and index. */
|
||||||
IFvalue ptemp; /* a value structure to package resistance into */
|
IFvalue ptemp; /* a value structure to package resistance into */
|
||||||
IFvalue *parm; /* a pointer to a value struct for function returns */
|
IFvalue *parm; /* a pointer to a value struct for function returns */
|
||||||
int which; /* which analysis we are performing */
|
int which; /* which analysis we are performing */
|
||||||
|
|
@ -446,6 +448,9 @@ dot_sens(char *line, CKTcircuit *ckt, INPtables *tab, struct card *current,
|
||||||
CKTnode *node1; /* the first node's node pointer */
|
CKTnode *node1; /* the first node's node pointer */
|
||||||
CKTnode *node2; /* the second node's node pointer */
|
CKTnode *node2; /* the second node's node pointer */
|
||||||
char *steptype; /* ac analysis, type of stepping function */
|
char *steptype; /* ac analysis, type of stepping function */
|
||||||
|
char *cp; /* Scan for filters. */
|
||||||
|
|
||||||
|
extern char **Sens_filter; /* cktsens.c */
|
||||||
|
|
||||||
which = ft_find_analysis("SENS");
|
which = ft_find_analysis("SENS");
|
||||||
if (which == -1) {
|
if (which == -1) {
|
||||||
|
|
@ -456,9 +461,10 @@ dot_sens(char *line, CKTcircuit *ckt, INPtables *tab, struct card *current,
|
||||||
IFC(newAnalysis, (ckt, which, "Sensitivity Analysis", &foo, task));
|
IFC(newAnalysis, (ckt, which, "Sensitivity Analysis", &foo, task));
|
||||||
|
|
||||||
/* Format is:
|
/* Format is:
|
||||||
* .sens <output>
|
* .sens <output> [<filter strings>]
|
||||||
* + [ac [dec|lin|oct] <pts> <low freq> <high freq> | dc ]
|
* + [ac [dec|lin|oct] <pts> <low freq> <high freq> | dc ]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Get the output voltage or current */
|
/* Get the output voltage or current */
|
||||||
INPgetTok(&line, &name, 0);
|
INPgetTok(&line, &name, 0);
|
||||||
/* name is now either V or I or a serious error */
|
/* name is now either V or I or a serious error */
|
||||||
|
|
@ -495,12 +501,44 @@ dot_sens(char *line, CKTcircuit *ckt, INPtables *tab, struct card *current,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
INPgetTok(&line, &name, 1);
|
/* Scan for filters for the parameter names to be varied.
|
||||||
if (name && !strcmp(name, "pct")) {
|
* INPgetTok() breaks on '*' so scan by hand.
|
||||||
ptemp.iValue = 1;
|
*/
|
||||||
GCA(INPapName, (ckt, which, foo, "pct", &ptemp));
|
|
||||||
INPgetTok(&line, &name, 1);
|
if (Sens_filter)
|
||||||
|
FREE(Sens_filter);
|
||||||
|
fidx = 0;
|
||||||
|
filters = -1; // Ensure room for NULL.
|
||||||
|
name = NULL;
|
||||||
|
while (*line && *line > ' ')
|
||||||
|
++line;
|
||||||
|
for (;;) {
|
||||||
|
int l;
|
||||||
|
|
||||||
|
while (*line && *line <= ' ')
|
||||||
|
++line;
|
||||||
|
if (!*line)
|
||||||
|
break;
|
||||||
|
cp = line;
|
||||||
|
while (*cp && *cp > ' ')
|
||||||
|
++cp;
|
||||||
|
l = (int)(cp - line);
|
||||||
|
name = TMALLOC(char, l + 1);
|
||||||
|
strncpy(name, line, l);
|
||||||
|
name[l] = 0;
|
||||||
|
line = cp;
|
||||||
|
if (!strcmp(name, "ac") || !strcmp(name, "dc"))
|
||||||
|
break;
|
||||||
|
if (fidx >= filters)
|
||||||
|
Sens_filter = TREALLOC(char *, Sens_filter, filters + 8);
|
||||||
|
filters += 8;
|
||||||
|
Sens_filter[fidx++] = name;
|
||||||
|
name = NULL;
|
||||||
}
|
}
|
||||||
|
if (Sens_filter) {
|
||||||
|
Sens_filter[fidx] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (name && !strcmp(name, "ac")) {
|
if (name && !strcmp(name, "ac")) {
|
||||||
INPgetTok(&line, &steptype, 1); /* get DEC, OCT, or LIN */
|
INPgetTok(&line, &steptype, 1); /* get DEC, OCT, or LIN */
|
||||||
ptemp.iValue = 1;
|
ptemp.iValue = 1;
|
||||||
|
|
@ -515,9 +553,10 @@ dot_sens(char *line, CKTcircuit *ckt, INPtables *tab, struct card *current,
|
||||||
} else if (name && *name && strcmp(name, "dc")) {
|
} else if (name && *name && strcmp(name, "dc")) {
|
||||||
/* Bad flag */
|
/* Bad flag */
|
||||||
LITERR("Syntax error: 'ac' or 'dc' expected.\n");
|
LITERR("Syntax error: 'ac' or 'dc' expected.\n");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
return (0);
|
if (name)
|
||||||
|
FREE(name);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue