Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
47ff9b73
Commit
47ff9b73
authored
Jan 01, 2012
by
Piotr Mitros
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
cjt's simulator
parent
20243182
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
885 additions
and
0 deletions
+885
-0
js/cktsim.js
+885
-0
No files found.
js/cktsim.js
0 → 100644
View file @
47ff9b73
//////////////////////////////////////////////////////////////////////////////
//
// Circuit simulator
//
//////////////////////////////////////////////////////////////////////////////
// Chris Terman, Dec. 2011
// create a circuit for simulation using "new cktsim.Circuit()"
// for modified nodal analysis (MNA) stamps see
// http://books.google.com/books?id=qhHsSlazGrQC&pg=PA44&lpg=PA44&dq=MNA+stamp+inductor&source=bl&ots=ThMq-FmhLo&sig=cTP1ld_fhIJbGPSBXPDbh3Xappk&hl=en&sa=X&ei=6wb-ToecFMHj0QH61-Fs&ved=0CFcQ6AEwAw#v=onepage&q=MNA%20stamp%20inductor&f=false
cktsim
=
(
function
()
{
///////////////////////////////////////////////////////////////////////////////
//
// Circuit
//
//////////////////////////////////////////////////////////////////////////////
// types of "nodes" in the linear system
T_VOLTAGE
=
0
;
T_CURRENT
=
1
;
function
Circuit
()
{
this
.
node_map
=
new
Array
();
this
.
ntypes
=
[];
this
.
initial_conditions
=
[];
// ic's for each element
this
.
devices
=
[];
// list of devices
this
.
device_map
=
new
Array
();
// map name -> device
this
.
end_of_timestep
=
[];
// list of devices to be called at end of each timestep
this
.
finalized
=
false
;
this
.
node_index
=
-
1
;
// for backward Euler: coeff0 = 1/timestep, coeff1 = 0
// for trapezoidal: coeff0 = 2/timestep, coeff1 = 1
this
.
coeff0
=
undefined
;
this
.
coeff1
=
undefined
;
}
// index of ground node
Circuit
.
prototype
.
gnd_node
=
function
()
{
return
-
1
;
}
// allocate a new node index
Circuit
.
prototype
.
node
=
function
(
name
,
ntype
,
ic
)
{
this
.
node_index
+=
1
;
if
(
name
)
this
.
node_map
[
name
]
=
this
.
node_index
;
this
.
ntypes
.
push
(
ntype
);
this
.
initial_conditions
.
push
(
ic
);
return
this
.
node_index
;
}
// call to finalize the circuit in preparation for simulation
Circuit
.
prototype
.
finalize
=
function
()
{
if
(
!
this
.
finalized
)
{
this
.
finalized
=
true
;
this
.
N
=
this
.
node_index
+
1
;
// number of nodes
// give each device a chance to finalize itself
for
(
var
i
=
this
.
devices
.
length
-
1
;
i
>=
0
;
--
i
)
this
.
devices
[
i
].
finalize
(
this
);
// set up augmented matrix and various temp vectors
this
.
matrix
=
new
Array
(
this
.
N
);
for
(
var
i
=
this
.
N
-
1
;
i
>=
0
;
--
i
)
this
.
matrix
[
i
]
=
new
Array
(
this
.
N
+
1
);
this
.
swap
=
new
Array
(
this
.
N
);
// keep track of row swaps during pivoting
this
.
soln
=
new
Array
(
this
.
N
);
// hold swapped solution
}
}
// load circuit from JSON netlist (see schematic.js)
Circuit
.
prototype
.
load_netlist
=
function
(
netlist
)
{
// set up mapping for ground node always called '0' in JSON netlist
this
.
node_map
[
'0'
]
=
this
.
gnd_node
();
// process each component in the JSON netlist (see schematic.js for format)
for
(
var
i
=
netlist
.
length
-
1
;
i
>=
0
;
--
i
)
{
var
component
=
netlist
[
i
];
var
type
=
component
[
0
];
// ignore wires, ground connections and view info
if
(
type
==
'view'
||
type
==
'w'
||
type
==
'g'
)
continue
;
var
properties
=
component
[
2
];
var
name
=
properties
[
'name'
];
// convert node names to circuit indicies
var
connections
=
component
[
3
];
for
(
var
j
=
connections
.
length
-
1
;
j
>=
0
;
--
j
)
{
var
node
=
connections
[
j
];
var
index
=
this
.
node_map
[
node
];
if
(
index
==
undefined
)
index
=
this
.
node
(
node
,
T_VOLTAGE
);
connections
[
j
]
=
index
;
}
// process the component
if
(
type
==
'r'
)
// resistor
this
.
r
(
connections
[
0
],
connections
[
1
],
properties
[
'r'
],
name
);
else
if
(
type
==
'c'
)
// capacitor
this
.
c
(
connections
[
0
],
connections
[
1
],
properties
[
'c'
],
name
);
else
if
(
type
==
'l'
)
// inductor
this
.
l
(
connections
[
0
],
connections
[
1
],
properties
[
'l'
],
name
);
else
if
(
type
==
'v'
)
// voltage source
this
.
v
(
connections
[
0
],
connections
[
1
],
properties
[
'value'
],
name
);
else
if
(
type
==
'i'
)
// current source
this
.
i
(
connections
[
0
],
connections
[
1
],
properties
[
'value'
],
name
);
else
if
(
type
==
'o'
)
// op amp
this
.
opamp
(
connections
[
0
],
connections
[
1
],
connections
[
2
],
properties
[
'A'
],
name
);
else
if
(
type
==
'n'
)
// n fet
this
.
fet
(
'n'
,
connections
[
0
],
connections
[
1
],
connections
[
2
],
properties
[
'sw'
],
properties
[
'sl'
],
name
);
else
if
(
type
==
'p'
)
// p fet
this
.
fet
(
'p'
,
connections
[
0
],
connections
[
1
],
connections
[
2
],
properties
[
'sw'
],
properties
[
'sl'
],
name
);
}
}
// DC analysis
Circuit
.
prototype
.
dc
=
function
()
{
this
.
finalize
();
// set up equations
this
.
initialize_linear_system
();
for
(
var
i
=
this
.
devices
.
length
-
1
;
i
>=
0
;
--
i
)
this
.
devices
[
i
].
load_dc
(
this
);
// solve for operating point
var
x
=
solve_linear_system
(
this
.
matrix
);
// create solution dictionary
var
result
=
new
Array
();
for
(
var
name
in
this
.
node_map
)
{
var
index
=
this
.
node_map
[
name
];
result
[
name
]
=
(
index
==
-
1
)
?
0
:
x
[
index
];
}
return
result
;
}
Circuit
.
prototype
.
r
=
function
(
n1
,
n2
,
v
,
name
)
{
// try to convert string value into numeric value, barf if we can't
if
((
typeof
v
)
==
'string'
)
{
v
=
parse_number
(
v
,
undefined
);
if
(
v
===
undefined
)
return
undefined
;
}
var
d
;
if
(
v
!=
0
)
{
d
=
new
Resistor
(
n1
,
n2
,
v
);
this
.
devices
.
push
(
d
);
if
(
name
)
this
.
device_map
[
name
]
=
d
;
}
else
return
this
.
v
(
n1
,
n2
,
0
,
name
);
// zero resistance == 0V voltage source
}
Circuit
.
prototype
.
c
=
function
(
n1
,
n2
,
v
,
name
)
{
// try to convert string value into numeric value, barf if we can't
if
((
typeof
v
)
==
'string'
)
{
v
=
parse_number
(
v
,
undefined
);
if
(
v
===
undefined
)
return
undefined
;
}
var
d
=
new
Capacitor
(
n1
,
n2
,
v
);
this
.
devices
.
push
(
d
);
if
(
name
)
this
.
device_map
[
name
]
=
d
;
return
d
;
}
Circuit
.
prototype
.
l
=
function
(
n1
,
n2
,
v
,
name
)
{
// try to convert string value into numeric value, barf if we can't
if
((
typeof
v
)
==
'string'
)
{
v
=
parse_number
(
v
,
undefined
);
if
(
v
===
undefined
)
return
undefined
;
}
var
branch
=
this
.
node
(
undefined
,
T_CURRENT
);
var
d
=
new
Inductor
(
n1
,
n2
,
branch
,
v
);
this
.
devices
.
push
(
d
);
if
(
name
)
this
.
device_map
[
name
]
=
d
;
return
d
;
}
Circuit
.
prototype
.
v
=
function
(
n1
,
n2
,
v
,
name
)
{
var
branch
=
this
.
node
(
undefined
,
T_CURRENT
);
var
d
=
new
VSource
(
n1
,
n2
,
branch
,
v
);
this
.
devices
.
push
(
d
);
if
(
name
)
this
.
device_map
[
name
]
=
d
;
return
d
;
}
Circuit
.
prototype
.
i
=
function
(
n1
,
n2
,
v
,
name
)
{
var
d
=
new
ISource
(
n1
,
n2
,
v
);
this
.
devices
.
push
(
d
);
if
(
name
)
this
.
device_map
[
name
]
=
d
;
return
d
;
}
///////////////////////////////////////////////////////////////////////////////
//
// Support for creating and solving a system of linear equations
//
////////////////////////////////////////////////////////////////////////////////
// model circuit using a linear system of the form Ax = b where
// A is an nxn matrix of conductances and branch voltages
// b is an n-element vector of sources
// x is an n-element vector of unknowns (node voltages, branch currents)
// Knowns (A and b) are stored in an augmented matrix M = [A | b]
// Matrix is stored as an array of arrays: M[row][col].
// set augmented matrix to zero
Circuit
.
prototype
.
initialize_linear_system
=
function
()
{
for
(
var
i
=
this
.
N
-
1
;
i
>=
0
;
--
i
)
{
var
row
=
this
.
matrix
[
i
];
for
(
var
j
=
this
.
N
;
j
>=
0
;
--
j
)
// N+1 entries
row
[
j
]
=
0
;
}
}
// add conductance between two nodes to matrix A.
// Index of -1 refers to ground node
Circuit
.
prototype
.
add_conductance
=
function
(
i
,
j
,
g
)
{
if
(
i
>=
0
)
{
this
.
matrix
[
i
][
i
]
+=
g
;
if
(
j
>=
0
)
{
this
.
matrix
[
i
][
j
]
-=
g
;
this
.
matrix
[
j
][
i
]
-=
g
;
this
.
matrix
[
j
][
j
]
+=
g
;
}
}
else
if
(
j
>=
0
)
this
.
matrix
[
j
][
j
]
+=
g
;
}
// add individual conductance to A
Circuit
.
prototype
.
add_to_A
=
function
(
i
,
j
,
v
)
{
if
(
i
>=
0
&&
j
>=
0
)
this
.
matrix
[
i
][
j
]
+=
v
;
}
// add source info to vector b
Circuit
.
prototype
.
add_to_b
=
function
(
i
,
v
)
{
if
(
i
>=
0
)
this
.
matrix
[
i
][
this
.
N
]
+=
v
;
}
// solve Ax=b and return vector x given augmented matrix [A | b]
// Uses Gaussian elimination with partial pivoting
function
solve_linear_system
(
M
)
{
var
N
=
M
.
length
;
// augmented matrix M has N rows, N+1 columns
var
temp
,
i
,
j
;
// gaussian elimination
for
(
var
col
=
0
;
col
<
N
;
col
++
)
{
// find pivot: largest abs(v) in this column of remaining rows
var
max_v
=
Math
.
abs
(
M
[
col
][
col
]);
var
max_col
=
col
;
for
(
i
=
col
+
1
;
i
<
N
;
i
++
)
{
temp
=
Math
.
abs
(
M
[
i
][
col
]);
if
(
temp
>
max_v
)
{
max_v
=
temp
;
max_col
=
i
;
}
}
// if no value found, generate a small conductance to gnd
// otherwise swap current row with pivot row
if
(
max_v
==
0
)
M
[
col
][
col
]
=
1
e
-
10
;
else
{
temp
=
M
[
col
];
M
[
col
]
=
M
[
max_col
];
M
[
max_col
]
=
temp
;
}
// now eliminate this column for all subsequent rows
for
(
i
=
col
+
1
;
i
<
N
;
i
++
)
{
temp
=
M
[
i
][
col
]
/
M
[
col
][
col
];
// multiplier we'll use for current row
if
(
temp
!=
0
)
// subtract current row from row we're working on
// remember to process b too!
for
(
j
=
col
;
j
<=
N
;
j
++
)
M
[
i
][
j
]
-=
M
[
col
][
j
]
*
temp
;
}
}
// matrix is now upper triangular, so solve for elements of x starting
// with the last row
var
x
=
new
Array
(
N
);
for
(
i
=
N
-
1
;
i
>=
0
;
--
i
)
{
temp
=
M
[
i
][
N
];
// grab b[i] from augmented matrix as RHS
// subtract LHS term from RHS using known x values
for
(
j
=
N
-
1
;
j
>
i
;
--
j
)
temp
-=
M
[
i
][
j
]
*
x
[
j
];
// now compute new x value
x
[
i
]
=
temp
/
M
[
i
][
i
];
}
// return solution
return
x
;
}
// test solution code, expect x = [2,3,-1]
//M = [[2,1,-1,8],[-3,-1,2,-11],[-2,1,2,-3]];
//x = solve_linear_system(M);
//y = 1; // so we have place to set a breakpoint :)
///////////////////////////////////////////////////////////////////////////////
//
// Device base class
//
////////////////////////////////////////////////////////////////////////////////
function
Device
()
{
}
// complete initial set up of device
Device
.
prototype
.
finalize
=
function
()
{
}
// reset internal state of the device to initial value
Device
.
prototype
.
reset
=
function
()
{
}
// load linear system equations for dc analysis
// (inductors shorted and capacitors opened)
Device
.
prototype
.
load_dc
=
function
(
ckt
)
{
}
// load linear system equations for tran analysis
Device
.
prototype
.
load_tran
=
function
(
ckt
,
soln
)
{
}
// load linear system equations for ac analysis:
// current sources open, voltage sources shorted
// linear models at operating point for everyone else
Device
.
prototype
.
load_ac
=
function
(
ckt
)
{
}
// called with there's an accepted time step
Device
.
prototype
.
end_of_timestep
=
function
(
ckt
)
{
}
// return time of next breakpoint for the device
Device
.
prototype
.
breakpoint
=
function
(
time
)
{
return
undefined
;
}
///////////////////////////////////////////////////////////////////////////////
//
// Parse numbers in engineering notation
//
///////////////////////////////////////////////////////////////////////////////
// convert first character of argument into an integer
function
ord
(
ch
)
{
return
ch
.
charCodeAt
(
0
);
}
// convert string argument to a number, accepting usual notations
// (hex, octal, binary, decimal, floating point) plus engineering
// scale factors (eg, 1k = 1000.0 = 1e3).
// return default if argument couldn't be interpreted as a number
function
parse_number
(
s
,
default_v
)
{
s
=
s
.
toLowerCase
();
// make life simple for ourselves
var
slen
=
s
.
length
;
var
multiplier
=
1
;
var
result
=
0
;
var
index
=
0
;
// skip leading whitespace
while
(
index
<
slen
&&
s
.
charAt
(
index
)
<=
' '
)
index
+=
1
;
if
(
index
==
slen
)
return
default_v
;
// check for leading sign
if
(
s
.
charAt
(
index
)
==
'-'
)
{
multiplier
=
-
1
;
index
+=
1
;
}
else
if
(
s
.
charAt
(
index
)
==
'+'
)
index
+=
1
;
var
start
=
index
;
// remember where digits start
// if leading digit is 0, check for hex, octal or binary notation
if
(
index
>=
slen
)
return
default_v
;
else
if
(
s
.
charAt
(
index
)
==
'0'
)
{
index
+=
1
;
if
(
index
>=
slen
)
return
0
;
if
(
s
.
charAt
(
index
)
==
'x'
)
{
// hex
while
(
true
)
{
index
+=
1
;
if
(
index
>=
slen
)
break
;
if
(
s
.
charAt
(
index
)
>=
'0'
&&
s
.
charAt
(
index
)
<=
'9'
)
result
=
result
*
16
+
ord
(
s
.
charAt
(
index
))
-
ord
(
'0'
);
else
if
(
s
.
charAt
(
index
)
>=
'a'
&&
s
.
charAt
(
index
)
<=
'f'
)
result
=
result
*
16
+
ord
(
s
.
charAt
(
index
))
-
ord
(
'a'
)
+
10
;
else
break
;
}
return
result
*
multiplier
;
}
else
if
(
s
.
charAt
(
index
)
==
'b'
)
{
// binary
while
(
true
)
{
index
+=
1
;
if
(
index
>=
slen
)
break
;
if
(
s
.
charAt
(
index
)
>=
'0'
&&
s
.
charAt
(
index
)
<=
'1'
)
result
=
result
*
2
+
ord
(
s
.
charAt
(
index
))
-
ord
(
'0'
);
else
break
;
}
return
result
*
multiplier
;
}
else
if
(
s
.
charAt
(
index
)
!=
'.'
)
{
// octal
while
(
true
)
{
if
(
s
.
charAt
(
index
)
>=
'0'
&&
s
.
charAt
(
index
)
<=
'7'
)
result
=
result
*
8
+
ord
(
s
.
charAt
(
index
))
-
ord
(
'0'
);
else
break
;
index
+=
1
;
if
(
index
>=
slen
)
break
;
}
return
result
*
multiplier
;
}
}
// read decimal integer or floating-point number
while
(
true
)
{
if
(
s
.
charAt
(
index
)
>=
'0'
&&
s
.
charAt
(
index
)
<=
'9'
)
result
=
result
*
10
+
ord
(
s
.
charAt
(
index
))
-
ord
(
'0'
);
else
break
;
index
+=
1
;
if
(
index
>=
slen
)
break
;
}
// fractional part?
if
(
index
<
slen
&&
s
.
charAt
(
index
)
==
'.'
)
{
while
(
true
)
{
index
+=
1
;
if
(
index
>=
slen
)
break
;
if
(
s
.
charAt
(
index
)
>=
'0'
&&
s
.
charAt
(
index
)
<=
'9'
)
{
result
=
result
*
10
+
ord
(
s
.
charAt
(
index
))
-
ord
(
'0'
);
multiplier
*=
0.1
;
}
else
break
;
}
}
// if we haven't seen any digits yet, don't check
// for exponents or scale factors
if
(
index
==
start
)
return
default_v
;
// type of multiplier determines type of result:
// multiplier is a float if we've seen digits past
// a decimal point, otherwise it's an int or long.
// Up to this point result is an int or long.
result
*=
multiplier
;
// now check for exponent or engineering scale factor. If there
// is one, result will be a float.
if
(
index
<
slen
)
{
var
scale
=
s
.
charAt
(
index
);
index
+=
1
;
if
(
scale
==
'e'
)
{
var
exponent
=
0
;
multiplier
=
10.0
;
if
(
index
<
slen
)
{
if
(
s
.
charAt
(
index
)
==
'+'
)
index
+=
1
;
else
if
(
s
.
charAt
(
index
)
==
'-'
)
{
index
+=
1
;
multiplier
=
0.1
;
}
}
while
(
index
<
slen
)
{
if
(
s
.
charAt
(
index
)
>=
'0'
&&
s
.
charAt
(
index
)
<=
'9'
)
{
exponent
=
exponent
*
10
+
ord
(
s
.
charAt
(
index
))
-
ord
(
'0'
);
index
+=
1
;
}
else
break
;
}
while
(
exponent
>
0
)
{
exponent
-=
1
;
result
*=
multiplier
;
}
}
else
if
(
scale
==
't'
)
result
*=
1
e12
;
else
if
(
scale
==
'g'
)
result
*=
1
e9
;
else
if
(
scale
==
'k'
)
result
*=
1
e3
;
else
if
(
scale
==
'u'
)
result
*=
1
e
-
6
;
else
if
(
scale
==
'n'
)
result
*=
1
e
-
9
;
else
if
(
scale
==
'p'
)
result
*=
1
e
-
12
;
else
if
(
scale
==
'f'
)
result
*=
1
e
-
15
;
else
if
(
scale
==
'm'
)
{
if
(
index
+
1
<
slen
)
{
if
(
s
.
charAt
(
index
)
==
'e'
&&
s
.
charAt
(
index
+
1
)
==
'g'
)
result
*=
1
e6
;
else
if
(
s
.
charAt
(
index
)
==
'i'
&&
s
.
charAt
(
index
+
1
)
==
'l'
)
result
*=
25.4
e
-
6
;
}
else
result
*=
1
e
-
3
;
}
else
return
default_v
;
}
// ignore any remaining chars, eg, 1kohms returns 1000
return
result
;
}
///////////////////////////////////////////////////////////////////////////////
//
// Sources
//
///////////////////////////////////////////////////////////////////////////////
// argument is a string describing the source's value:
// <value> or dc(<value>) -- constant value
// pulse(<vinit>,<vpulse>,<tdelay>,<trise>,<tfall>,<t_width>,<t_period>)
// sin(<voffset>,<vamplitude>,<hz>,<tdelay>,<phase_offset_degrees>)
// pwl(<time>,<value>,...) -- piecewise linear: time,value pairs
// returns an object with the following attributes:
// value(t) -- compute source value at time t
// inflection_point(t) -- compute time after t when a time point is needed
// dc -- value at time 0
function
parse_source
(
v
)
{
// generic parser: parse v as either <value> or <fun>(<value>,...)
var
src
=
new
Object
();
src
.
value
=
function
(
t
)
{
return
0
;
}
// overridden below
src
.
inflection_point
=
function
(
t
)
{
return
undefined
;
};
// may be overridden below
// see if there's a "(" in the description
var
index
=
v
.
indexOf
(
'('
);
var
ch
;
if
(
index
>=
0
)
{
src
.
fun
=
v
.
slice
(
0
,
index
);
// function name is before the "("
src
.
args
=
[];
// we'll push argument values onto this list
var
end
=
v
.
indexOf
(
')'
,
index
);
if
(
end
==
-
1
)
end
=
v
.
length
;
index
+=
1
;
// start parsing right after "("
while
(
index
<
end
)
{
// figure out where next argument value starts
ch
=
v
.
charAt
(
index
);
if
(
ch
<=
' '
)
{
index
++
;
continue
;
}
// and where it ends
var
arg_end
=
v
.
indexOf
(
','
,
index
);
if
(
arg_end
==
-
1
)
arg_end
=
end
;
// parse and save result in our list of arg values
src
.
args
.
push
(
parse_number
(
v
.
slice
(
index
,
arg_end
),
undefined
));
index
=
arg_end
+
1
;
}
}
else
{
src
.
fun
=
'dc'
;
src
.
args
=
[
parse_number
(
v
,
0
)];
}
// post-processing for constant sources
if
(
src
.
fun
==
'dc'
)
{
var
value
=
src
.
args
[
0
];
if
(
value
===
undefined
)
value
=
0
;
src
.
value
=
function
(
t
)
{
return
value
;
}
// closure
}
// post-processing for pulsed sources
else
if
(
src
.
fun
==
'pulse'
)
{
var
v1
=
arg_value
(
src
.
args
,
0
,
0
);
// default init value: 0V
var
v2
=
arg_value
(
src
.
args
,
1
,
1
);
// default plateau value: 1V
var
td
=
Math
.
min
(
0
,
arg_value
(
src
.
args
,
2
,
0
));
// time pulse starts
var
tr
=
Math
.
abs
(
arg_value
(
src
.
args
,
3
,
1
e
-
9
));
// default rise time: 1ns
var
tf
=
Math
.
abs
(
arg_value
(
src
.
args
,
4
,
1
e
-
9
));
// default rise time: 1ns
var
pw
=
Math
.
abs
(
arg_value
(
src
.
args
,
5
,
1
e9
));
// default pulse width: "infinite"
var
per
=
Math
.
abs
(
arg_value
(
src
.
args
,
6
,
1
e9
));
// default period: "infinite"
var
t1
=
td
;
// time when v1 -> v2 transition starts
var
t2
=
t1
+
tr
;
// time when v1 -> v2 transition ends
var
t3
=
t2
+
pw
;
// time when v2 -> v1 transition starts
var
t4
=
t3
+
tf
;
// time when v2 -> v1 transition ends
// return value of source at time t
src
.
value
=
function
(
t
)
{
// closure
var
tmod
=
Math
.
fmod
(
t
,
per
);
if
(
tmod
<
t1
)
return
v1
;
else
if
(
tmod
<
t2
)
return
v1
+
(
v2
-
v1
)
*
(
tmod
-
t1
)
/
(
t2
-
t1
);
else
if
(
tmod
<
t3
)
return
v2
;
else
if
(
tmod
<
t4
)
return
v2
+
(
v1
-
v2
)
*
(
tmod
-
t3
)
/
(
t4
-
t3
);
else
return
v1
;
}
// return time of next inflection point after time t
src
.
inflection_point
=
function
(
t
)
{
// closure
var
tstart
=
per
*
Math
.
floor
(
t
/
per
);
var
tmod
=
t
-
tstart
;
if
(
tmod
<
t1
)
return
tstart
+
t1
;
else
if
(
t
<
t2
)
return
tstart
+
t2
;
else
if
(
t
<
t3
)
return
tstart
+
t3
;
else
if
(
t
<
t4
)
return
tstart
+
t4
;
else
return
tstart
+
per
+
t1
;
}
}
// post-processing for sinusoidal sources
else
if
(
src
.
fun
==
'sin'
)
{
var
degrees_to_radians
=
2
*
Math
.
PI
/
360.0
;
var
voffset
=
arg_value
(
src
.
args
,
0
,
0
);
// default offset voltage: 0V
var
va
=
arg_value
(
src
.
args
,
1
,
1
);
// default amplitude: -1V to 1V
var
freq
=
arg_value
(
src
.
args
,
2
,
1
);
// default frequency: 1Hz
var
td
=
Math
.
min
(
0
,
arg_value
(
src
.
args
,
3
,
0
));
// default time delay: 0sec
var
phase
=
arg_value
(
src
.
args
,
4
,
0
);
// default phase offset: 0 degrees
phase
/=
360.0
;
// return value of source at time t
src
.
value
=
function
(
t
)
{
// closure
if
(
t
<
td
)
return
voffset
+
Math
.
sin
(
2
*
Math
.
PI
*
phase
);
else
{
t
-=
td
;
return
voffset
+
Math
.
sin
(
2
*
Math
.
PI
*
(
freq
*
(
t
-
td
)
+
phase
));
}
}
// return time of next inflection point after time t
src
.
inflection_point
=
function
(
t
)
{
// closure
if
(
t
<
td
)
return
td
;
else
return
undefined
;
}
}
// to do:
// post-processing for piece-wise linear sources
// object has all the necessary info to compute the source value and inflection points
src
.
dc
=
src
.
value
(
0
);
// DC value is value at time 0
return
src
;
}
// helper function: return args[index] if present, else default_v
function
arg_value
(
args
,
index
,
default_v
)
{
if
(
index
<
args
.
length
)
{
var
result
=
args
[
index
];
if
(
result
===
undefined
)
result
=
default_v
;
return
result
;
}
else
return
default_v
;
}
// we need fmod in the Math library!
Math
.
fmod
=
function
(
numerator
,
denominator
)
{
var
quotient
=
Math
.
floor
(
numerator
/
denominator
);
return
numerator
-
quotient
*
denominator
;
}
///////////////////////////////////////////////////////////////////////////////
//
// Sources
//
///////////////////////////////////////////////////////////////////////////////
function
VSource
(
npos
,
nneg
,
branch
,
v
)
{
Device
.
call
(
this
);
this
.
src
=
parse_source
(
v
);
this
.
npos
=
npos
;
this
.
nneg
=
nneg
;
this
.
branch
=
branch
;
}
VSource
.
prototype
=
new
Device
();
VSource
.
prototype
.
construction
=
VSource
;
// load linear system equations for dc analysis
VSource
.
prototype
.
load_dc
=
function
(
ckt
,
soln
)
{
// MNA stamp for independent voltage source
ckt
.
add_to_A
(
this
.
branch
,
this
.
npos
,
1.0
);
ckt
.
add_to_A
(
this
.
branch
,
this
.
nneg
,
-
1.0
);
ckt
.
add_to_A
(
this
.
npos
,
this
.
branch
,
1.0
);
ckt
.
add_to_A
(
this
.
nneg
,
this
.
branch
,
-
1.0
);
ckt
.
add_to_b
(
this
.
branch
,
this
.
src
.
value
(
ckt
.
time
));
}
// load linear system equations for tran analysis (just like DC)
VSource
.
prototype
.
load_tran
=
function
(
ckt
,
soln
)
{
this
.
load_dc
(
ckt
);
}
// return time of next breakpoint for the device
VSource
.
prototype
.
breakpoint
=
function
(
time
)
{
return
this
.
src
.
inflection_point
(
time
);
}
// small signal model: short circuit
VSource
.
prototype
.
load_ac
=
function
()
{
// use branch row in matrix to set following constraint on system:
// v_pos - v_neg = 0V
ckt
.
add_to_A
(
this
.
branch
,
this
.
npos
,
1.0
);
ckt
.
add_to_A
(
this
.
branch
,
this
.
nneg
,
-
1.0
);
// ckt.add_to_b(this.branch,0); // adding 0 isn't necessary!
}
function
ISource
(
npos
,
nneg
,
v
)
{
Device
.
call
(
this
);
this
.
src
=
parse_source
(
v
);
this
.
npos
=
npos
;
this
.
nneg
=
nneg
;
}
ISource
.
prototype
=
new
Device
();
ISource
.
prototype
.
construction
=
ISource
;
// load linear system equations for dc analysis
ISource
.
prototype
.
load_dc
=
function
(
ckt
)
{
var
i
=
this
.
src
.
value
(
ckt
.
time
);
// MNA stamp for independent current source
ckt
.
add_to_b
(
this
.
npos
,
-
i
);
// current flow into npos
ckt
.
add_to_b
(
this
.
nneg
,
i
);
// and out of nneg
}
// load linear system equations for tran analysis (just like DC)
ISource
.
prototype
.
load_tran
=
function
(
ckt
,
soln
)
{
this
.
load_dc
(
ckt
);
}
// return time of next breakpoint for the device
ISource
.
prototype
.
breakpoint
=
function
(
time
)
{
return
this
.
src
.
inflection_point
(
time
);
}
// small signal model: open circuit
ISource
.
prototype
.
load_ac
=
function
()
{
}
///////////////////////////////////////////////////////////////////////////////
//
// Resistor
//
///////////////////////////////////////////////////////////////////////////////
function
Resistor
(
n1
,
n2
,
v
)
{
Device
.
call
(
this
);
this
.
n1
=
n1
;
this
.
n2
=
n2
;
this
.
g
=
1.0
/
v
;
}
Resistor
.
prototype
=
new
Device
();
Resistor
.
prototype
.
construction
=
Resistor
;
Resistor
.
prototype
.
load_dc
=
function
(
ckt
)
{
// MNA stamp for admittance g
ckt
.
add_conductance
(
this
.
n1
,
this
.
n2
,
this
.
g
);
}
Resistor
.
prototype
.
load_tran
=
function
(
ckt
,
soln
)
{
this
.
load_dc
(
ckt
);
}
Resistor
.
prototype
.
load_ac
=
function
(
ckt
)
{
this
.
load_dc
(
ckt
);
}
///////////////////////////////////////////////////////////////////////////////
//
// Capacitor
//
///////////////////////////////////////////////////////////////////////////////
function
Capacitor
(
n1
,
n2
,
v
)
{
Device
.
call
(
this
);
this
.
n1
=
n1
;
this
.
n2
=
n2
;
this
.
value
=
v
;
}
Capacitor
.
prototype
=
new
Device
();
Capacitor
.
prototype
.
construction
=
Capacitor
;
// capacitor is modeled as a current source (ieq) in parallel with a conductance (geq)
Capacitor
.
prototype
.
reset
=
function
()
{
this
.
q
=
0
;
// state variable (charge)
this
.
i
=
0
;
// dstate/dt (current)
this
.
prev_q
=
0
;
// last iteration
this
.
prev_i
=
0
;
}
Capacitor
.
prototype
.
finalize
=
function
(
ckt
)
{
// call us at the end of each timestep
ckt
.
end_of_timestep
.
push
(
this
);
}
Capacitor
.
prototype
.
end_of_timestep
=
function
(
ckt
)
{
// update state when timestep is accepted
this
.
prev_q
=
this
.
q
;
this
.
prev_i
=
this
.
i
;
}
Capacitor
.
prototype
.
load_dc
=
function
(
ckt
)
{
// open circuit
}
Capacitor
.
prototype
.
load_tran
=
function
(
ckt
,
soln
)
{
var
vcap
=
((
this
.
n1
>=
0
)
?
soln
[
this
.
n1
]
:
0
)
-
((
this
.
n2
>=
0
)
?
soln
[
this
.
n2
]
:
0
);
this
.
q
=
this
.
value
*
vcap
;
// set charge
// integrate
// for backward Euler: coeff0 = 1/timestep, coeff1 = 0
// for trapezoidal: coeff0 = 2/timestep, coeff1 = 1
this
.
i
=
ckt
.
coeff0
*
(
this
.
q
-
this
.
prev_q
)
-
ckt
.
coeff1
*
this
.
prev_i
;
var
ieq
=
this
.
i
-
ckt
.
coeff0
*
this
.
q
;
var
geq
=
ckt
.
coeff0
*
this
.
value
;
// MNA stamp for admittance geq
ckt
.
add_conductance
(
this
.
n1
,
this
.
n2
,
geq
);
// MNA stamp for current source ieq
ckt
.
add_to_b
(
this
.
n1
,
-
ieq
);
ckt
.
add_to_b
(
this
.
n2
,
ieq
);
}
Capacitor
.
prototype
.
load_ac
=
function
(
ckt
)
{
ckt
.
add_conductance
(
this
.
n1
,
this
.
n2
,
ckt
.
omega
*
this
.
value
);
}
///////////////////////////////////////////////////////////////////////////////
//
// Inductor
//
///////////////////////////////////////////////////////////////////////////////
function
Inductor
(
n1
,
n2
,
branch
,
v
)
{
Device
.
call
(
this
);
this
.
n1
=
n1
;
this
.
n2
=
n2
;
this
.
branch
=
branch
;
this
.
value
=
v
;
}
Inductor
.
prototype
=
new
Device
();
Inductor
.
prototype
.
construction
=
Inductor
;
// inductor is modeled as a voltage source (veq) with impedance (geq)
Inductor
.
prototype
.
reset
=
function
()
{
this
.
flux
=
0
;
// state variable (flux)
this
.
v
=
0
;
// dstate/dt (voltage)
this
.
prev_flux
=
0
;
// last iteration
this
.
prev_v
=
0
;
}
Inductor
.
prototype
.
finalize
=
function
(
ckt
)
{
// call us at the end of each timestep
ckt
.
end_of_timestep
.
push
(
this
);
}
Inductor
.
prototype
.
end_of_timestep
=
function
(
ckt
)
{
// update state when timestep is accepted
this
.
prev_flux
=
this
.
flux
;
this
.
prev_v
=
this
.
v
;
}
Inductor
.
prototype
.
load_dc
=
function
(
ckt
)
{
// short circuit: veq = 0, req = 0
ckt
.
add_to_A
(
this
.
n1
,
this
.
branch
,
1
);
ckt
.
add_to_A
(
this
.
branch
,
this
.
n1
,
1
);
ckt
.
add_to_A
(
this
.
n2
,
this
.
branch
,
-
1
);
ckt
.
add_to_A
(
this
.
branch
,
this
.
n2
,
-
1
);
}
Inductor
.
prototype
.
load_tran
=
function
(
ckt
,
soln
)
{
this
.
flux
=
this
.
value
*
soln
[
this
.
branch
];
// set flux
// integrate
// for backward Euler: coeff0 = 1/timestep, coeff1 = 0
// for trapezoidal: coeff0 = 2/timestep, coeff1 = 1
this
.
v
=
ckt
.
coeff0
*
(
this
.
flux
-
this
.
prev_flux
)
-
ckt
.
coeff1
*
this
.
prev_v
;
var
veq
=
this
.
v
-
ckt
.
coeff0
*
this
.
flux
;
var
req
=
ckt
.
coeff0
*
this
.
value
;
// MNA stamp for voltage source with impedance
ckt
.
add_to_b
(
this
.
branch
,
veq
);
ckt
.
add_to_A
(
this
.
branch
,
this
.
branch
,
-
req
);
ckt
.
add_to_A
(
this
.
n1
,
this
.
branch
,
1
);
ckt
.
add_to_A
(
this
.
branch
,
this
.
n1
,
1
);
ckt
.
add_to_A
(
this
.
n2
,
this
.
branch
,
-
1
);
ckt
.
add_to_A
(
this
.
branch
,
this
.
n2
,
-
1
);
}
Inductor
.
prototype
.
load_ac
=
function
(
ckt
)
{
ckt
.
add_to_A
(
this
.
branch
,
this
.
branch
,
-
ckt
.
omega
*
this
.
value
);
ckt
.
add_to_A
(
this
.
n1
,
this
.
branch
,
1
);
ckt
.
add_to_A
(
this
.
branch
,
this
.
n1
,
1
);
ckt
.
add_to_A
(
this
.
n2
,
this
.
branch
,
-
1
);
ckt
.
add_to_A
(
this
.
branch
,
this
.
n2
,
-
1
);
}
///////////////////////////////////////////////////////////////////////////////
//
// Module definition
//
///////////////////////////////////////////////////////////////////////////////
var
module
=
{
'Circuit'
:
Circuit
,
}
return
module
;
}());
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment