AdvancedTop, Main, Index
This section provides advanced examples from the examples
folder in the root directory of SpiceGenTcl. Rather than focusing on basic operations, we highlight advanced use cases that showcase the full capabilities of both the package and the Tcl language. List of availible examples:
- Monte-Carlo simulation - "examples/ngspice/advanced/monte_carlo.tcl" and "examples/xyce/advanced/monte_carlo.tcl" file
- Parameters extraction of diode model parameters - "examples/ngspice/advanced/diode_extract.tcl" file
Monte-Carlo simulationTop, Main, Index
This example demonstrates multiple runs of a simple filter circuit and the collection of the resulting statistical distribution of frequency bandwidths. The original circuit source is from the ngspice source distribution. The target filter circuit is:
The filter is a 3rd-order Chebyshev bandpass. The first step is to build the circuit and obtain the magnitude of the transfer characteristic:
# create top-level circuit set circuit [Circuit new {Monte-Carlo}] # add elements to circuit $circuit add [Vac new 1 n001 0 -ac 1] $circuit add [R new 1 n002 n001 -r 141] $circuit add [R new 2 0 out -r 141] C create c1 1 out 0 -c 1e-9 L create l1 1 out 0 -l 10e-6 C create c2 2 n002 0 -c 1e-9 L create l2 2 n002 0 -l 10e-6 C create c3 3 out n003 -c 250e-12 L create l3 3 n003 n002 -l 40e-6 foreach elem [list c1 l1 c2 l2 c3 l3] { $circuit add $elem } $circuit add [Ac new -variation oct -n 100 -fstart 250e3 -fstop 10e6] #set simulator with default set simulator [Batch new {batch1}] # attach simulator object to circuit $circuit configure -simulator $simulator
Here, we use a different method for creating a class instance: create
instead of new
. With create
, we can directly set a custom object reference name, rather than relying on the automatically generated one by Tcl.
C create c1 1 out 0 -c 1e-9
Keep in mind that c1
is an object reference, not the name of a variable storing the reference. Therefore, it can be used as an object command directly, without the need for a $
.
To calculate the magnitude of the transfer function in dB scale from the output voltage phasor, we create a procedure:
proc calcDbMag {re im} { set mag [= {sqrt($re*$re+$im*$im)}] set db [= {10*log($mag)}] return $db } proc calcDbMagVec {vector} { foreach value $vector { lappend db [calcDbMag [@ $value 0] [@ $value 1]] } return $db }
calcDbMagVec | Procedure that apply calcDbMag to list of complex values. |
Run and plot the result:
# run simulation $circuit runAndRead # get data dictionary set data [$circuit getDataDict] set trace [calcDbMagVec [dget $data v(out)]] set freqs [dget $data frequency] foreach x $freqs y $trace { lappend xydata [list [@ $x 0] $y] } puts [findBW [lmap freq $freqs {@ $freq 0}] $trace -10] set chartTransMag [ticklecharts::chart new] $chartTransMag Xaxis -name "Frequency, Hz" -minorTick {show "True"} -type "log" -splitLine {show "True"} $chartTransMag Yaxis -name "Magnitude, dB" -minorTick {show "True"} -type "value" -splitLine {show "True"} $chartTransMag SetOptions -title {} -tooltip {trigger "axis"} -animation "False" -toolbox {feature {dataZoom {yAxisIndex "none"}}} -grid {left "10%" right "15%"} -backgroundColor "#212121" $chartTransMag Add "lineSeries" -data $xydata -showAllSymbol "nothing" -symbolSize "1" set fbasename [file rootname [file tail [info script]]] $chartTransMag Render -outfile [file normalize [file join .. html_charts ${fbasename}_typ.html]] -width 1000px
We define pass bandwidth by edge values -10dB, to find them we use next procedure:
proc findBW {freqs vals trigVal} { # calculate bandwidth of results set freqsLen [llength $freqs] for {set i 0} {$i<$freqsLen} {incr i} { set iVal [@ $vals $i] set ip1Val [@ $vals [+ $i 1]] if {($iVal<=$trigVal) && ($ip1Val>=$trigVal)} { set freqStart [@ $freqs $i] } elseif {($iVal>=$trigVal) && ($ip1Val<=$trigVal)} { set freqEnd [@ $freqs $i] } } set bw [= {$freqEnd-$freqStart}] return $bw }
Find bandwidth:
puts [findBW [lmap freq $freqs {@ $freq 0}] $trace -10]
The value is 1.086255 Mhz.
Our goal is to obtain a distribution of bandwidths by varying the filter parameters. To generate random values for these parameters, we use the built-in functions of the math::statistics
package from Tcllib. The parameters can be distributed either normally or uniformly. For uniform distribution, we use ::math::statistics::random-uniform xmin xmax number
; for normal distribution, we use ::math::statistics::random-normal mean stdev number
. For uniform distribution, we define the following min and max limits for each C and L element:
set distLimits [dcreate c1 [dcreate min 0.9e-9 max 1.1e-9] l1 [dcreate min 9e-6 max 11e-6] c2 [dcreate min 0.9e-9 max 1.1e-9] l2 [dcreate min 9e-6 max 11e-6] c3 [dcreate min 225e-12 max 275e-12] l3 [dcreate min 36e-6 max 44e-6]]
We can specify different numbers of simulations; the more runs we perform, the more accurate the representation becomes. For example, we set the number of simulations to 1,000 runs with 15 intervals for constructing a boxplot:
# set number of simulations set mcRuns 100 set numOfIntervals 15
Now we ready to run simulations 1000 times and collect results:
# loop in which we run simulation with uniform distribution for {set i 0} {$i<$mcRuns} {incr i} { #set elements values according to uniform distribution foreach elem [list c1 l1 c2 l2 c3 l3] { $elem setParamValue [string index $elem 0] [random-uniform {*}[dict values [dget $distLimits $elem]] 1] } # run simulation $circuit runAndRead # get data dictionary set data [$circuit getDataDict] # get results if {$i==0} { set freqs [dget $data frequency] foreach freq $freqs { lappend freqRes [@ $freq 0] } } # get vout frequency curve lappend traceListUni [calcDbMagVec [dget $data v(out)]] # calculate bandwidths values lappend bwsUni [findBW $freqRes [@ $traceListUni end] -10] }
To obtain the distribution, we need to determine reasonable limits based on the minimum and maximum of the generated bandwidth values. Using the specified number of intervals (15), we apply the following procedure:
proc createIntervals {data numOfIntervals} { set intervals [::math::statistics::minmax-histogram-limits [tcl::mathfunc::min {*}$data] [tcl::mathfunc::max {*}$data] $numOfIntervals] lappend intervalsStrings [format "<=%.2e" [@ $intervals 0]] for {set i 0} {$i<[- [llength $intervals] 1]} {incr i} { lappend intervalsStrings [format "%.2e-%.2e" [@ $intervals $i] [@ $intervals [+ $i 1]]] } return [dcreate intervals $intervals intervalsStr $intervalsStrings] }
Here, we use the dedicated procedure from the statistics package: ::math::statistics::minmax-histogram-limits min max number
. Additionally, we construct strings in form leftVal-rightVal
that represent the intervals on the boxplot chart. We call this procedure and store the results:
set uniIntervals [createIntervals $bwsUni $numOfIntervals]
Finally, to obtain the expected histogram for a uniform distribution, we use the following procedure with the built-in function: ::math::statistics::histogram-uniform xmin xmax limits number
:
proc createDist {data intervals} { set dist [::math::statistics::histogram $intervals $data] return [lrange $dist 0 end-1] }
Call the createDist
procedure to calculate the histogram y-axis values, which correspond to the number of bandwidths within each interval:
set normDist [createDist $bwsNorm [dget $normIntervals intervals]]
The same sequence of steps is applied for the normal distribution. We assume that std = (xmax - xmin) / 6
, where xmax
and xmin
are the limits of the uniform distribution.
# set parameter's normal distributions limits set normalLimits [dcreate c1 [dcreate mean 1e-9 std [/ 0.1e-9 3]] l1 [dcreate mean 10e-6 std [/ 1e-6 3]] c2 [dcreate mean 1e-9 std [/ 0.1e-9 3]] l2 [dcreate mean 10e-6 std [/ 1e-6 3]] c3 [dcreate mean 250e-12 std [/ 25e-12 3]] l3 [dcreate mean 40e-6 std [/ 4e-6 3]]] ## loop in which we run simulation with normal distribution for {set i 0} {$i<$mcRuns} {incr i} { #set elements values according to normal distribution foreach elem [list c1 l1 c2 l2 c3 l3] { $elem setParamValue [string index $elem 0] [random-normal {*}[dict values [dget $normalLimits $elem]] 1] } # run simulation $circuit runAndRead # get data dictionary set data [$circuit getDataDict] # get results if {$i==0} { set freqs [dget $data frequency] foreach freq $freqs { lappend freqRes [@ $freq 0] } } # get vout frequency curve lappend traceListNorm [calcDbMagVec [dget $data v(out)]] # calculate bandwidths values lappend bwsNorm [findBW $freqRes [@ $traceListNorm end] -10] } # get distribution of bandwidths with normal parameters distribution set normIntervals [createIntervals $bwsNorm $numOfIntervals] set normDist [createDist $bwsNorm [dget $normIntervals intervals]]
Finally, we plot resulted distributions:
# plot results with ticklecharts # chart for uniformly distributed parameters set chartUni [ticklecharts::chart new] $chartUni Xaxis -name "Frequency intervals, Hz" -data [list [dget $uniIntervals intervalsStr]] -axisTick {show "True" alignWithLabel "True"} -axisLabel {interval "0" rotate "45" fontSize "8"} $chartUni Yaxis -name "Bandwidths per interval" -minorTick {show "True"} -type "value" $chartUni SetOptions -title {} -tooltip {trigger "axis"} -animation "False" -toolbox {feature {dataZoom {yAxisIndex "none"}}} -backgroundColor "#212121" $chartUni Add "barSeries" -data [list $uniDist] # chart for normally distributed parameters set chartNorm [ticklecharts::chart new] $chartNorm Xaxis -name "Frequency intervals, Hz" -data [list [dget $normIntervals intervalsStr]] -axisTick {show "True" alignWithLabel "True"} -axisLabel {interval "0" rotate "45" fontSize "8"} $chartNorm Yaxis -name "Bandwidths per interval" -minorTick {show "True"} -type "value" $chartNorm SetOptions -title {} -tooltip {trigger "axis"} -animation "False" -toolbox {feature {dataZoom {yAxisIndex "none"}}} -backgroundColor "#212121" $chartNorm Add "barSeries" -data [list $normDist] # create multiplot set layout [ticklecharts::Gridlayout new] $layout Add $chartNorm -bottom "10%" -height "35%" -width "75%" $layout Add $chartUni -bottom "60%" -height "35%" -width "75%" set fbasename [file rootname [file tail [info script]]] $layout Render -outfile [file normalize [file join .. html_charts $fbasename.html]] -height 800px -width 1200px
We can clearly see the difference between normal and uniform distributions; the intervals are close due to setting the standard deviation of the normal distribution as std = (xmax - xmin) / 6.
We can also take the uniform intervals and calculate the normal distribution values at these intervals:
# find distribution of normal distributed values in uniform intervals set normDistWithUniIntervals [createDist $bwsNorm [dget $uniIntervals intervals]] set chartCombined [ticklecharts::chart new] $chartCombined Xaxis -name "Frequency intervals, Hz" -data [list [dget $uniIntervals intervalsStr]] -axisTick {show "True" alignWithLabel "True"} -axisLabel {interval "0" rotate "45" fontSize "8"} $chartCombined Yaxis -name "Bandwidths per interval" -minorTick {show "True"} -type "value" $chartCombined SetOptions -title {} -legend {} -tooltip {trigger "axis"} -animation "False" -toolbox {feature {dataZoom {yAxisIndex "none"}}} -grid {left "10%" right "15%"} -backgroundColor "#212121" $chartCombined Add "barSeries" -data [list $uniDist] -name "Uniform" $chartCombined Add "barSeries" -data [list $normDistWithUniIntervals] -name "Normal" $chartCombined Render -outfile [file normalize [file join .. html_charts ${fbasename}_combined.html]] -height 800px -width 1200px
Parameters extraction of diode model parametersTop, Main, Index
In this advanced example we will do curve fitting procedure to extract parameters of diode model. We use measured (emulated with more advanced diode model generated data, with added small distorsion and make non-equal voltage steps) forward current characteristic of diode.
This example demands two additional Tcl packages to make fast interpolation and Levenberg-Marquardt optimization algorithm:
Also, the tclcsv package is used for data reading.
The main steps of this procedure are:
- Create cost function that calculates difference between simulated and measured data, and which should be minimized
- Select the region of optimization
- Set list of parameters that will be extracted, define lower and upper limits for them.
- Run optimization
- Use resulted values as initial values for next optimization in different region
- ...
- Repeat until the quality of fitting reaches required level.
First step is to define the cost function that will be used by the optimizer:
proc diodeIVcalc {xall pdata args} { dict with pdata {} $model setParamValue is [@ $xall 0] n [@ $xall 1] rs [@ $xall 2] ikf [@ $xall 3] $vSrc setParamValue start $vMin stop $vMax incr $vStep $circuit runAndRead set data [$circuit getDataDict] foreach iVal $i iSim [lmap i [dget $data i(va)] {= {-$i}}] { lappend fvec [= {log(abs($iVal))-log(abs($iSim))}] lappend fval $iSim } return [dcreate fvec $fvec fval $fval] }
The input arguments are:
xall
- list with fitting parameters, in our case, the parameters of the diode model.pdata
- the dictionary containing information that allows to calculate cost function, in our case it contains multiple fields that will be explained below.args
- any additional arguments, usually is used for passing information for calculating analytic derivatives.
Let me expand the values in pdata
dictionary:
v
- list of applied voltages valuesi
- list of measured current valuescircuit
- top circuit object simulating forward diode currentmodel
- diode model objectvMin
- start of the voltage regionvMax
- end of the voltage regionvStep
- size of voltage stepvSrc
- voltage source object
First line creates variables with the same name as keys of pdata
dictionary:
dict with pdata {}
Then we set parameters values of the diode model object using ::SpiceGenTcl::Model::setParamValue method:
$model setParamValue is [@ $xall 0] n [@ $xall 1] rs [@ $xall 2] ikf [@ $xall 3]
The same we do with voltage source element:
$vSrc setParamValue start $vMin stop $vMax incr $vStep
Then we run circuit with new parameters and read data back to calculate residuals:
$circuit runAndRead set data [$circuit getDataDict]
Calculating residuals and save simulated data in the loop:
foreach iVal $i iSim [lmap i [dget $data i(va)] {= {-$i}}] { lappend fvec [= {log(abs($iVal))-log(abs($iSim))}] lappend fval $iSim }
The residuals is calculated with the next formula:
f = log ⎛|i |⎞ - log ⎛|i |⎞ ⎝| meas|⎠ ⎝| sim|⎠
We use logarithm of values because the current values span across multiple orders, from 1e-8 to 1e-2, that creates issue for fitting algorithm.
Procedure returns dictionary that contains lists with residuals and diode current under the keys fvec
and fval
, and fvec
key is mandatory because list under this key is used by optimization procedure.
To run simulation we need to build circuit and define necessary objects:
set diodeModel [DiodeModel new diomod -is 1e-12 -n 1.0 -rs 30 -cj0 1e-9 -trs1 0.001 -xti 5 -ikf 1e-4] set vSrc [Dc new -src va -start 0 -stop 2 -incr 0.02] set circuit [Circuit new {diode IV}] $circuit add [D new 1 anode 0 -model diomod -area 1] $circuit add [Vdc new a anode 0 -dc 0] $circuit add $diodeModel $circuit add $vSrc set tempSt [Temp new 25] $circuit add $tempSt $circuit configure -simulator [Batch new {batch1}]
We save object references for diode model and voltage source into dedicated variable to use it in cost function - it will be saved into pdata
dictionary.
Next, we read measured data from .csv file with tclcsv
package:
set file [open [file join $fileDataPath iv25.csv]] set ivTemp25 [csv_read -startline 1 $file] close $file set vRaw [lmap elem $ivTemp25 {@ $elem 0}] set iRaw [lmap elem $ivTemp25 {@ $elem 1}]
The loaded data is (with selected regions of fitting):
Fitting in first regionTop, Main, Index
First region we want to fit is the region of ideal diode, it spans from minimum voltage to approximately 0.85V. We set these limits in variables vMin
, vMax
and define the equal step of voltage vStep
.
set vMin [min {*}$vRaw] set vMax 0.85 set vStep 0.02
We use fixed voltage step instead of data points defined in measured data with non-equal steps to ease the defining the sweep of voltage in simulator - we can use DC sweep of voltage source with fixed step. So, we use linear interpolation to get values of current at even grid with defined minimum and maximum values. Then, we create pdata
dictionary with corresponding values:
set vInterp [lseq $vMin to $vMax by $vStep] set iInterp [lin1d -x $vRaw -y $iRaw -xi $vInterp] set pdata [dcreate v $vInterp i $iInterp circuit $circuit model $diodeModel vMin $vMin vMax $vMax vStep $vStep vSrc $vSrc]
Command lin1d
is imported from tclinterp
package.
Parameters that we need to find (extract) are:
is | Saturation current. |
n | Ideality factor. |
rs | Series resistance. |
ikf | Forward knee current. |
In first region the parameters of interest are n
and is0
- it affects current curve the most in this region.
Parameter are represented as special objects ::tclopt::ParameterMpfit
from tclopt
package, where we can define initial values and lower/upper limits:
set iniPars [list 1e-14 1.0 30 1e-4] set par0 [::tclopt::ParameterMpfit new is [@ $iniPars 0] -lowlim 1e-17 -uplim 1e-12] set par1 [::tclopt::ParameterMpfit new n [@ $iniPars 1] -lowlim 0.5 -uplim 2] set par2 [::tclopt::ParameterMpfit new rs [@ $iniPars 2] -fixed -lowlim 1e-10 -uplim 100] set par3 [::tclopt::ParameterMpfit new ikf [@ $iniPars 3] -fixed -lowlim 1e-12 -uplim 0.1]
For rs
and ikf
parameters we specify option -fixed
to make them constant during the first region fitting.
Optimization is done with Levenberg-Marquardt algorithm based optimizator from tclopt
package. First step is to create ::tclopt::Mpfit
object that defines the optimization parameters:
set optimizer [::tclopt::Mpfit new -funct diodeIVcalc -m [llength $vInterp] -pdata $pdata]
The arguments we pass here are:
-funct | Name of the procedure calculating the residuals. |
-m | Number of fitting points. |
-pdata | Dictionary that is passed to cost function procedure. |
Then we add parameters to $optimizer
object, and the order of adding them should match the order of the elements list xall
we pass values to cost function:
$optimizer addPars $par0 $par1 $par2 $par3
Then we can run the simulation, read the resulted list of parameters x
and print formatted values:
set result [$optimizer run] set resPars [dget $result x] puts [format "is=%.3e, n=%.3e, rs=%.3e, ikf=%.3e" {*}[dget $result x]]
The result is:
is=5.771e-17, n=1.050e+00, rs=3.000e+01, ikf=1.000e-04
You can notice that is
and n
now have different values, and rs
and ikf
remains the same because of -fixed
option.
Fitting in second regionTop, Main, Index
Now we are ready to fit curve in the second ohmic/high-injection region. It started at the upper end of first region and continue until the end of the data. We interpolate this range with the same voltage step:
set vMin 0.85 set vMax [max {*}$vRaw] set vInterp [lseq $vMin to $vMax by $vStep] set iInterp [lin1d -x $vRaw -y $iRaw -xi $vInterp]
We will use the same pdata
variable containing dictionary, but change the current and voltage data and limits:
dset pdata v $vInterp dset pdata i $iInterp dset pdata vMin $vMin dset pdata vMax $vMax
We make is
and n
parameters fixed this time, and free the rs
and ikf
:
$par0 configure -fixed 1 -initval [@ $resPars 0] $par1 configure -fixed 1 -initval [@ $resPars 1] $par2 configure -fixed 0 $par3 configure -fixed 0
You can notice that we supply the resulted values of is
and n
as the initial values to the second fitting, so we have two parameters already fitted before start the next optimization.
Replace $optimizer
object fields with the new values:
$optimizer configure -m [llength $vInterp] -pdata $pdata
Run, read result and print new parameters values:
set result [$optimizer run] set fittedIdiode [dget [diodeIVcalc [dget $result x] $pdata] fval] set resPars [dget $result x] puts [format "is=%.3e, n=%.3e, rs=%.3e, ikf=%.3e" {*}[dget $result x]]
The result is:
is=5.771e-17, n=1.050e+00, rs=5.430e+00, ikf=3.069e-04
Now we can see that rs
and ikf
were changed during fitting, just as planned.
Fitting the whole curveTop, Main, Index
The last step is the fitting to the whole curve, with initial values of parameters replaced with resulted values.
Again, set voltage limits to minimum and maximum of the data, interpolate it and replace data in $pdata
dictionary:
set vMin [min {*}$vRaw] set vMax [max {*}$vRaw] set vInterp [lseq $vMin to $vMax by $vStep] set iInterp [lin1d -x $vRaw -y $iRaw -xi $vInterp] dset pdata v $vInterp dset pdata i $iInterp dset pdata vMin $vMin dset pdata vMax $vMax
We unfix rs
and ikf
parameters, and set new limits within ±10% of previous values to forbid parameters becoming too different:
$par0 configure -fixed 0 -initval [@ $resPars 0] -lowlim [= {[@ $resPars 0]*0.9}] -uplim [= {[@ $resPars 0]*1.1}] $par1 configure -fixed 0 -initval [@ $resPars 1] -lowlim [= {[@ $resPars 1]*0.9}] -uplim [= {[@ $resPars 1]*1.1}] $par2 configure -initval [@ $resPars 2] -lowlim [= {[@ $resPars 2]*0.9}] -uplim [= {[@ $resPars 2]*1.1}] $par3 configure -initval [@ $resPars 3] -lowlim [= {[@ $resPars 3]*0.9}] -uplim [= {[@ $resPars 3]*1.1}]
The set new data in optimizer object, run and print results:
$optimizer configure -m [llength $vInterp] -pdata $pdata set result [$optimizer run] puts [format "is=%.3e, n=%.3e, rs=%.3e, ikf=%.3e" {*}[dget $result x]]
The result is:
is=6.348e-17, n=1.061e+00, rs=5.232e+00, ikf=3.376e-04
All parameters were changed to produce best fit for the whole curve.
Plotting resulted dataTop, Main, Index
Now we are ready to see the result of the fitting, we need to calculate curves with initial values and final values of parameters:
set initIdiode [dget [diodeIVcalc $iniPars $pdata] fval] set fittedVIdiode [lmap vVal $vInterp iVal $fittedIdiode {list $vVal $iVal}] set initVIdiode [lmap vVal $vInterp iVal $initIdiode {list $vVal $iVal}] set viRaw [lmap vVal $vRaw iVal $iRaw {list $vVal $iVal}]
Plot it with ticklecharts
package in linear and logarithmic scales:
set chart [ticklecharts::chart new] $chart Xaxis -name "v(anode), V" -minorTick {show "True"} -type "value" -splitLine {show "True"} -min "0.4" -max "1.6" $chart Yaxis -name "Idiode, A" -minorTick {show "True"} -type "value" -splitLine {show "True"} -min "0.0" -max "dataMax" $chart SetOptions -title {} -tooltip {trigger "axis"} -animation "False" -legend {} -toolbox {feature {dataZoom {yAxisIndex "none"}}} -grid {left "10%" right "15%"} -backgroundColor "#212121" $chart Add "lineSeries" -data $fittedVIdiode -showAllSymbol "nothing" -name "fitted" -symbolSize "4" $chart Add "lineSeries" -data $initVIdiode -showAllSymbol "nothing" -name "unfitted" -symbolSize "4" $chart Add "lineSeries" -data $viRaw -showAllSymbol "nothing" -name "measured" -symbolSize "4" set chartLog [ticklecharts::chart new] $chartLog Xaxis -name "v(anode), V" -minorTick {show "True"} -type "value" -splitLine {show "True"} -min "0.4" -max "1.6" $chartLog Yaxis -name "Idiode, A" -minorTick {show "True"} -type "log" -splitLine {show "True"} -min "dataMin" -max "0.1" $chartLog SetOptions -title {} -tooltip {trigger "axis"} -animation "False" -legend {} -toolbox {feature {dataZoom {yAxisIndex "none"}}} -grid {left "10%" right "15%"} -backgroundColor "#212121" $chartLog Add "lineSeries" -data $fittedVIdiode -showAllSymbol "nothing" -name "fitted" -symbolSize "4" $chartLog Add "lineSeries" -data $initVIdiode -showAllSymbol "nothing" -name "unfitted" -symbolSize "4" $chartLog Add "lineSeries" -data $viRaw -showAllSymbol "nothing" -name "measured" -symbolSize "4" set layout [ticklecharts::Gridlayout new] $layout Add $chartLog -bottom "5%" -height "40%" -width "80%" $layout Add $chart -bottom "55%" -height "40%" -width "80%" set fbasename [file rootname [file tail [info script]]] $layout Render -outfile [file normalize [file join .. html_charts $fbasename.html]] -height 900px -width 700px
On the plot we can see the initial simulated curve, the measured data and the final fitting result.