Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse plain text Q3D output #16

Merged
merged 4 commits into from
Oct 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,12 @@ plot(scatter(x=ω/(2π*1e9), y=angle.(S)), Layout(xaxis_title="Frequency [GHz]",
```

![](docs/Phase.png)

## Using with ANSYS Q3D Extractor

Plain text files containing RLGC parameters exported by ANSYS® Q3D Extractor®
software can be used to construct a `Circuit` object via `Circuit(file_path)`.
Currently only capacitance matrices are supported.

ANSYS and Q3D Extractor are registered trademarks of ANSYS, Inc. or its
subsidiaries in the United States or other countries.
1 change: 1 addition & 0 deletions src/AdmittanceModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ include("circuit.jl")
include("pso_model.jl")
include("blackbox.jl")
include("circuit_components.jl")
include("ansys.jl")

end
206 changes: 206 additions & 0 deletions src/ansys.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
#=
The functions here are intended to generate AdmittanceModels.Circuit objects
from ANSYS Q3D plain text output files containing RLGC parameters.

Here's a small example of the expected Q3D file format, valid for ANSYS Electronics Desktop 2016:

################################################################################################################
DesignVariation:\$DummyParam1='1' \$DummyParam2='1mm' \$dummy_param3='100e19pF' \$dummy_param4='1.2eUnits'
Setup1:LastAdaptive
Problem Type:C
C Units:farad, G Units:sie
Reduce Matrix:Original
Frequency: 4E+09 Hz

Capacitance Matrix
net_name_1 net_name_2
net_name_1 1.1924E-13 -1.3563E-16
net_name_2 -1.3563E-16 3.7607E-13

Conductance Matrix
net_name_1 net_name_2
net_name_1 2.4489E-09 -2.3722E-12
net_name_2 -2.3722E-12 7.7123E-09

Capacitance Matrix Coupling Coefficient
net_name_1 net_name_2
net_name_1 1 0.00064047 0.00018631
net_name_2 0.00064047 1 0.00010129

Conductance Matrix Coupling Coefficient
net_name_1 net_name_2
net_name_1 1 0.00054586 0.00017696
net_name_2 0.00054586 1 9.2987E-05
################################################################################################################

Each data block like \"Capacitance Matrix\" must start with a line enumerating
the N net names. These are the column labels for the given matrix. The matrix
rows are specified directly below this line. Each of the N row lines start with
a net name to label the row, followed by N space-separated float values for the
matrix elements. We expect these to be strings `parse(Float64, _)` can handle.
=#

using LinearAlgebra: diagind

"""
parse_value_and_unit(s::AbstractString)

Parse strings like "9.9e-9mm" => (9.9e-9, "mm").
"""
function parse_value_and_unit(s::AbstractString)
# Three capture groups in the regex intended to match
# (±X.X)(e-X)(unit)
m = match(r"(\-?[\d|\.]+)(e-?[\d]+)?(\D*)", s)
exp_str = m[2] == nothing ? "" : m[2]
num_str = m[1] * exp_str
num = parse(Float64, num_str)
unit_str = String(m[3])
(num, unit_str)
end

const matrix_types = Dict(
:capacitance => "Capacitance Matrix",
:conductance => "Conductance Matrix",
# :dc_inductance => "DC Inductance Matrix",
# :dc_resistance => "DC Resistance Matrix",
# :ac_inductance => "AC Inductance Matrix",
# :ac_resistance => "AC Resistance Matrix",
# :dc_plus_ac_resistance => "DCPlusAC Resistance Matrix",
)

const unit_names = Dict(
:capacitance => "C Units",
:conductance => "G Units"
)

const units_to_multipliers = Dict(
"fF" => 1e-15,
"pF" => 1e-12,
"nF" => 1e-9,
"uF" => 1e-6,
"mF" => 1e-3,
"farad" => 1.0,
"mSie" => 1e-3,
"sie" => 1.0
)

"""
parse_q3d_txt(filepath, matrix_type::Symbol)

Parses a plain text file generated by ANSYS Q3D containing RLGC simulation output.

# Args
- `q3d_file`: the path to the text file.
- `matrix_type`: A `Symbol` indicating the type of data to read. Currently,
symbols in $(collect(keys(matrix_types))) are the only available choices,
and only the units in $(collect(keys(units_to_multipliers))) are handled.

# Returns
- A tuple `(design_variation, net_names, matrix)`, where:
- `design_variation` is a `Dict` of design variables
- `net_names` is a `Vector` of net names (strings)
- `matrix` is the requested matrix
"""
function parse_q3d_txt(q3d_file, matrix_type::Symbol)
!haskey(matrix_types, matrix_type) && error("matrix type not implemented.")

matrix_name = matrix_types[matrix_type]
unit_name = unit_names[matrix_type]

# Windows uses \r\n for newlines, where Linux just uses \n.
# We work around that difference here by deleting all carriage return (\r)
# characters from the file.
linesep = "\n"
file_text = replace(read(q3d_file, String), "\r" => "")
file_chunks = split(file_text, linesep * linesep)
@assert length(file_chunks) > 1 "expected more than one block in Q3D plain text file."

is_chunk(header) = chunk -> startswith(chunk, header)
function get_chunk(header)
# the chunk may be missing, guard against errors
idx = findfirst(is_chunk(header), file_chunks)
!isnothing(idx) && return file_chunks[idx]
return nothing
end
get_chunk_line(chunk, line_num) = split(chunk, linesep)[line_num]

# The following parses lines like:
# DesignVariation:var1='12' var2='1e-05' var3='123um'
# into Dict("var1" => (12, ""),
# "var2" => (1e-5, ""),
# "var3" => (123, "um"))
design_variation_chunk = get_chunk("DesignVariation")
design_variation = if !isnothing(design_variation_chunk)
_, design_variation_data_str = split(get_chunk_line(design_variation_chunk, 1), ":")
design_strings = split(design_variation_data_str)
parse_design_kv((k, v)) = String(k) => parse_value_and_unit(strip(v, ['\'']))
Dict(parse_design_kv.(split.(design_strings, ['='])))
else
Dict{String, Tuple{Float64, String}}()
end

local unit_multiplier
for l in split(file_chunks[1], linesep)
if occursin("Units", l)
unit = Dict(split.(split(l, ", "), ':'))[unit_name]
!haskey(units_to_multipliers, unit) && error("unit $unit not implemented.")
unit_multiplier = units_to_multipliers[unit]
break
end
end
(@isdefined unit_multiplier) || error("units not given in Q3D plain text file.")

# Don't forget to include the line separator here.
# We typically have matrix_name = "Capacitance Matrix"
# But, these files can have a heading like "Capacitance Matrix Coupling Coefficient"
matrix_chunk = get_chunk(matrix_name * linesep)
net_names = String.(split(get_chunk_line(matrix_chunk, 2)))

get_matrix_row(row_idx) = parse.(Float64,
split(get_chunk_line(matrix_chunk, 2 + row_idx))[2:end]) .* unit_multiplier
matrix = reduce(hcat, get_matrix_row.(1:length(net_names)))

(design_variation, net_names, matrix)
end

"""
Circuit(q3d_file; matrix_types = [:capacitance])

Return an `AdmittanceModels.Circuit` from a plain text file generated by
ANSYS Q3D containing RLGC simulation output.

# Args
- `q3d_file`: the path to the text file.
- `matrix_type`: A list of symbols indicating which type of matrix data to read.
Currently, symbols in $(collect(keys(matrix_types))) are the only available
choices, and only the units in $(collect(keys(units_to_multipliers))) are handled.
"""
function Circuit(q3d_file; matrix_types = [:capacitance])
parse_dict = Dict(t => parse_q3d_txt(q3d_file, t) for t ∈ matrix_types)

# Check that all matrix types are defined over the same set of nets
# `take_only` asserts the iterator it's passed has only one element
nets = take_only(collect(Set(map(t -> t[2], values(parse_dict)))))
matrix_dict = Dict(t => parse_dict[t][3] for t ∈ matrix_types)

zero_mat() = zeros(length(nets), length(nets))

# The C and G matrices expected in AdmittanceModels.Circuit are the same
# as those parsed from Q3D, except with all diagonal entries set to 0 and
# all values made positive.
function prep_matrix(matrix_type)
x = get(matrix_dict, matrix_type, zero_mat())
x[diagind(x)] .= 0.
abs.(x)
end

k = zero_mat()
g = prep_matrix(:conductance)
c = prep_matrix(:capacitance)
return Circuit(k, g, c, nets)
end

function take_only(xs)
@assert length(xs) == 1 "Expected $xs to have one element."
return xs[1]
end
48 changes: 48 additions & 0 deletions test/data/dummy_gc_1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
DesignVariation:dummy1='-1.23' dummy2='9e-07mm' dummy3='-1.0e21pF' dummy4='1.2eunits'
Setup1:LastAdaptive
Problem Type:C
C Units:pF, G Units:mSie
Reduce Matrix:Original
Frequency: 1E+009 Hz

Capacitance Matrix
net1 net2 net3 net4
net1 0.011991 -0.0017236 -0.0035891 -0.00021927
net2 -0.0017236 0.012106 -0.0045205 -0.00013048
net3 -0.0035891 -0.0045205 0.04501 -0.00043332
net4 -0.00021927 -0.00013048 -0.00043332 0.0037523

Capacitance Matrix Coupling Coefficient
net1 net2 net3 net4
net1 1 0.14306 0.15449 0.032688
net2 0.14306 1 0.19366 0.019359
net3 0.15449 0.19366 1 0.033343
net4 0.032688 0.019359 0.033343 1

Spice Capacitance Matrix
net1 net2 net3 net4
net1 0.0064594 0.0017236 0.0035891 0.00021927
net2 0.0017236 0.0057315 0.0045205 0.00013048
net3 0.0035891 0.0045205 0.036467 0.00043332
net4 0.00021927 0.00013048 0.00043332 0.0029693

Conductance Matrix
net1 net2 net3 net4
net1 0 0 0 0
net2 0 0 0 0
net3 0 0 0 0
net4 0 0 0 0

Conductance Matrix Coupling Coefficient
net1 net2 net3 net4
net1 0 0 0 0
net2 0 0 0 0
net3 0 0 0 0
net4 0 0 0 0

Spice Conductance Matrix
net1 net2 net3 net4
net1 0 -0 -0 -0
net2 -0 0 -0 -0
net3 -0 -0 0 -0
net4 -0 -0 -0 0
48 changes: 48 additions & 0 deletions test/data/dummy_gc_2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Setup1:LastAdaptive
Problem Type:C
C Units:pF, G Units:kSie
Reduce Matrix:Original
Frequency: 1E+009 Hz

Capacitance Matrix
net1 net2 net3 net4
net1 0.011991 -0.0017236 -0.0035891 -0.00021927
net2 -0.0017236 0.012106 -0.0045205 -0.00013048
net3 -0.0035891 -0.0045205 0.04501 -0.00043332
net4 -0.00021927 -0.00013048 -0.00043332 0.0037523

Capacitance Matrix Coupling Coefficient
net1 net2 net3 net4
net1 1 0.14306 0.15449 0.032688
net2 0.14306 1 0.19366 0.019359
net3 0.15449 0.19366 1 0.033343
net4 0.032688 0.019359 0.033343 1

Spice Capacitance Matrix
net1 net2 net3 net4
net1 0.0064594 0.0017236 0.0035891 0.00021927
net2 0.0017236 0.0057315 0.0045205 0.00013048
net3 0.0035891 0.0045205 0.036467 0.00043332
net4 0.00021927 0.00013048 0.00043332 0.0029693

Conductance Matrix
net1 net2 net3 net4
net1 0 0 0 0
net2 0 0 0 0
net3 0 0 0 0
net4 0 0 0 0

Conductance Matrix Coupling Coefficient
net1 net2 net3 net4
net1 0 0 0 0
net2 0 0 0 0
net3 0 0 0 0
net4 0 0 0 0

Spice Conductance Matrix
net1 net2 net3 net4
net1 0 -0 -0 -0
net2 -0 0 -0 -0
net3 -0 -0 0 -0
net4 -0 -0 -0 0

46 changes: 46 additions & 0 deletions test/data/dummy_gc_3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Setup1:LastAdaptive
Problem Type:C
Reduce Matrix:Original
Frequency: 1E+009 Hz

Capacitance Matrix
net1 net2 net3 net4
net1 0.011991 -0.0017236 -0.0035891 -0.00021927
net2 -0.0017236 0.012106 -0.0045205 -0.00013048
net3 -0.0035891 -0.0045205 0.04501 -0.00043332
net4 -0.00021927 -0.00013048 -0.00043332 0.0037523

Capacitance Matrix Coupling Coefficient
net1 net2 net3 net4
net1 1 0.14306 0.15449 0.032688
net2 0.14306 1 0.19366 0.019359
net3 0.15449 0.19366 1 0.033343
net4 0.032688 0.019359 0.033343 1

Spice Capacitance Matrix
net1 net2 net3 net4
net1 0.0064594 0.0017236 0.0035891 0.00021927
net2 0.0017236 0.0057315 0.0045205 0.00013048
net3 0.0035891 0.0045205 0.036467 0.00043332
net4 0.00021927 0.00013048 0.00043332 0.0029693

Conductance Matrix
net1 net2 net3 net4
net1 0 0 0 0
net2 0 0 0 0
net3 0 0 0 0
net4 0 0 0 0

Conductance Matrix Coupling Coefficient
net1 net2 net3 net4
net1 0 0 0 0
net2 0 0 0 0
net3 0 0 0 0
net4 0 0 0 0

Spice Conductance Matrix
net1 net2 net3 net4
net1 0 -0 -0 -0
net2 -0 0 -0 -0
net3 -0 -0 0 -0
net4 -0 -0 -0 0
Empty file added test/data/empty.txt
Empty file.
5 changes: 5 additions & 0 deletions test/data/one_block.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Setup1:LastAdaptive
Problem Type:C
C Units:pF, G Units:mSie
Reduce Matrix:Original
Frequency: 1E+009 Hz
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ include("test_circuit.jl")
include("test_pso_model.jl")
include("test_blackbox.jl")
include("test_circuit_components.jl")
include("test_ansys.jl")
include("../paper/radiative_loss.jl")
include("../paper/hybridization.jl")
Loading