(* $Id: RFGraphics.m,v 1.17.2.1 2003/01/29 12:31:23 hennig Exp $ *)

(* {{{ Mathematica meta tags *)

(* :Title: Analog Insydes RF Graphics *)

(* :Authors: Dr.-Ing. Eckhard Hennig, eckhard@kaninkolo.de *)

(* :Summary: This module contains graphics functions for RF applications. *)

(* :Context: AnalogInsydes`RF`RFGraphics`  *)

(* :Package Version: $Revision: 1.17.2.1 $ *)

(* :Mathematica Version: > 3.0 *)

(* :Copyright: Copyright (C) 2001-2003 by Eckhard Hennig

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)

(* :History:
 * $Log: RFGraphics.m,v $
 * Revision 1.17.2.1  2003/01/29 12:31:23  hennig
 * + GPL.
 *
 *)

(* :Keywords: Smith chart *)

(* :Sources: 
  [1] H. G. Unger, Elektromagnetische Wellen auf Leitungen, 3. Auflage,
      Huethig, Heidelberg, 1991
*)

(* :Warning: *)

(* :Limitation: *)

(* :Discussion: *)

(* }}} *)

(* {{{ Version check *)

(******************************************************************************)
(* Analog Insydes RF Graphics Functions                                       *)
(******************************************************************************)

If[ $VersionNumber < 3.0,
  Print["This program requires Mathematica Version 3.0 or higher."];
  Abort[]
];

(* }}} *)

BeginPackage[ "AnalogInsydes`RF`RFGraphics`",
              "AnalogInsydes`RF`RFCommon`",
              "AnalogInsydes`Master`",
              "AnalogInsydes`Graphics`",
              "AnalogInsydes`Common`",
              "AnalogInsydes`Miscellaneous`",
              "Graphics`Graphics`",
              "Graphics`Common`GraphicsCommon`",
              "Graphics`Legend`" ]

(* {{{ Protection declarations, exported; Unprotect *)

(******************************************************************************)
(* Protection for exported symbols                                            *)
(******************************************************************************)

`Private`$ExportedProtectedSymbols = {
  AdmittancePoint,
  ChartStyle,
  ConductanceCircle,
  DataConversion,
  Decorations,
  GridResolution,
  High,
  ImpedancePoint,
  LabelOffset,
  LabelStretchFactor,
  LabelText,
  Low,
  MajorGridStyle,
  Medium,
  MinorGridStyle,
  PointColor,
  ResistanceCircle,
  SmithChart,
  VSWRCircle
}

(******************************************************************************)
(* Unprotection for exported symbols                                          *)
(******************************************************************************)

Unprotect @@ `Private`$ExportedProtectedSymbols;

(* }}} *)

(* {{{ Usage declarations *)

(******************************************************************************)
(* Usage declarations                                                         *)
(******************************************************************************)

AdmittancePoint::usage = "AdmittancePoint[Y, opts] yields a set of graphics
primitives to mark the point in a Smith chart which represents the admittance
Y. This function can be used as argument of the Decorations option."

ChartStyle::usage = "ChartStyle is an option for SmithChart, specifying whether
to draw an impedance or admittance grid. Available chart styles are Impedance
and Admittance."

ConductanceCircle::usage = "ConductanceCircle[Z, opts] yields a set of graphics
primitives to mark the circle of constant conductance associated with the
impedance Z. This function can be used as argument of the Decorations option."

DataConversion::usage = "DataConversion is an option for SmithChart. With
DataConversion->True, input values are assumed to represent impedances and are
converted to reflection coefficients before being displayed. With
DataConversion->False, input values are assumed to already represent reflection
coefficients."

Decorations::usage = "Decorations is an option for SmithChart, specifying a
list of decorations to be added to the Smith chart."

GridResolution::usage = "GridResolution is an option for SmithChart, specifying
the resolution of the chart grid. Available grid resolutions are Low, Medium,
and High."

LabelOffset::usage = "LabelOffset is an option for SmithChart, specifying the
displacement of the labels of the horizontal axis from the corresponding grid
positions."

LabelPosition::usage = "LabelPosition is an option for VSWRCircle, specifying
the position of the text label attached to the circle."

LabelStretchFactor::usage = "LabelStretchFactor is an option for SmithChart,
specifying the relative distance of the outside axis labels from the center of
the chart."

MajorGridStyle::usage = "MajorGridStyle is an option for SmithChart, specifying
the graphics style to be used to draw the major grid."

MinorGridStyle::usage = "MinorGridStyle is an option for SmithChart, specifying
the graphics style to be used to draw the minor grid."

High::usage = "High is an option value for GridStyle."

ImpedancePoint::usage = "ImpedancePoint[Z, opts] yields a set of graphics
primitives to mark the point in a Smith chart which represents the impedance
Z. This function can be used as argument of the Decorations option."

Low::usage = "Low is an option value for GridStyle."

Medium::usage = "Medium is an option value for GridStyle."

PointColor::usage = "PointColor is an option for some Smith chart decoration
functions, specifying the color of a point drawn on the chart."

ResistanceCircle::usage = "ResistanceCircle[Z, opts] yields a set of graphics
primitives to mark the circle of constant resistance associated with the
impedance Z. This function can be used as argument of the Decorations option."

SmithChart::usage = "SmithChart[Z, {var, fstart, fend}] draws the trajectory of
the reflection cofficient associated with the impedance Z[var] in the frequency
interval [fstart..fend] in a Smith chart. SmithChart[{Z1, Z2, ...}, {var,
fstart, fend}, opts] draws the reflection coefficients of several impedances."

VSWRCircle::usage = "VSWRCircle[vswr, opts] yields a set of graphics primitives
to mark a circle of constant voltage standing wave ratio in a Smith chart. This
function can be used as argument of the Decorations option."

(* }}} *)

(* {{{ Error messages *)

(******************************************************************************)
(* Error messages                                                             *)
(******************************************************************************)

(* Copy error messages from BodePlot. *)
SmithChart::frng = BodePlot::frng 
SmithChart::nres = BodePlot::nres 
SmithChart::ppts = BodePlot::ppts
SmithChart::prng = BodePlot::prng
SmithChart::ntrn = BodePlot::ntrn

VSWRCircle::vswr = "VSWR must be a real number greater than 1.0; received `1`."

Option::real2 = "Value of option `1` -> `2` is not a list of two real
numbers. Using `1` -> `3`."

(* }}} *)


Begin["`Private`"]

(* {{{ VersionID *)

(******************************************************************************)
(* VersionID                                                                  *)
(******************************************************************************)

$VersionId = "$Id: RFGraphics.m,v 1.17.2.1 2003/01/29 12:31:23 hennig Exp $"

(* }}} *)

(* {{{ Option checking rules *)

ChartStyle /:
CheckOption[ChartStyle, value_] :=
  If[ !MatchQ[value, Impedance | Admittance],
    Message[Option::value1, ChartStyle, {Impedance, Admittance}, Impedance];
    Impedance,
    value ]

DataConversion /:
CheckOption[DataConversion, value_] :=
  If[ NotBooleanQ[value],
    Message[Option::boolval, DataConversion, value, True];
    True,
    value ]

GridResolution /:
CheckOption[GridResolution, value_] :=
  If[ !MatchQ[value, Low | Medium | High],
    Message[Option::value1, GridResolution, {Low, Medium, High}, Medium];
    Medium,
    value ]
    
LabelOffset /:
CheckOption[LabelOffset, value_] :=
  If[ !MatchQ[N[value], {_Real, _Real}],
    Message[Option::real2, LabelOffset, value, {0.05, -0.03}];
    {0.05, -0.03},
    N[value] ]

LabelPosition /:
CheckOption[LabelPosition, value_] :=
  If[ !MatchQ[N[value], Automatic | {_Real, _Real}],
    Message[Option::real2, LabelOffset, value, Automatic];
    Automatic,
    N[value] ]

LabelStretchFactor /:
CheckOption[LabelStretchFactor, value_] :=
  If[ !Positive[N[value]],
    Message[Option::posreal, LabelStretchFactor, value, 1.05];
    1.05,
    N[value] ]

Unprotect[PointSize];
PointSize /:
CheckOption[PointSize, value_] :=
  If[ !Positive[N[value]],
    Message[Option::posreal, PointSize, value, 0.02];
    0.02,
    N[value] ]
Protect[PointSize]

(* }}} *)

(* {{{ Global constants *)

(******************************************************************************)
(* Smith chart grid line definitions for low, medium, and high-resolution     *)
(* grid.                                                                      *)
(******************************************************************************)

MajorRealCircles[Low] = {
  0.0,
  1.0
}

MinorRealCircles[Low] = {
  {0.5, 1.0},
  2.0
}

MajorRealCircles[Medium] = {
  0.0,
  1.0
}

MinorRealCircles[Medium] = {
  {0.2, 3.0},
  {0.5, 5.0},
  {2.0, 10.0}, 
  {3.0, 10.0},
  5.0,
  10.0,
  20.0
}

MajorRealCircles[High] = {
  0.0,
  1.0
}

MinorRealCircles[High] = {
  {0.1, 1.0},
  {0.2, 3.0},
  {0.3, 1.0},
  {0.4, 1.0},
  {0.5, 5.0},
  {0.6, 1.0},
  {0.8, 1.0},
  {1.2, 2.0},
  {1.5, 3.0},
  {2.0, 10.0}, 
  {3.0, 10.0},
  5.0,
  10.0,
  20.0
}

MajorImaginaryCircles[Low] = {
  -1.0,
  0.0,
  1.0
}

MinorImaginaryCircles[Low] = {
  {-2.0, 2.0},
  {-0.5, 1.0},
  {0.5, 1.0},
  {2.0, 2.0}
}

MajorImaginaryCircles[Medium] = {
  {-1.0, 10.0},
  0.0,
  {1.0, 10.0}
}

MinorImaginaryCircles[Medium] = {
  -10.0,
  {-5.0, 20.0},
  {-3.0, 10.0},
  {-2.0, 5.0},
  {-0.5, 5.0},
  {-0.2, 3.0},
  {0.2, 3.0},
  {0.5, 5.0},
  {2.0, 5.0},
  {3.0, 10.0},
  {5.0, 20.0},
  10.0
}

MajorImaginaryCircles[High] = {
  {-1.0, 10.0},
  0.0,
  {1.0, 10.0}
}

MinorImaginaryCircles[High] = {
  -10.0,
  {-5.0, 20.0},
  {-3.0, 10.0},
  {-2.0, 5.0},
  {-1.2, 2.0},
  {-1.5, 3.0},
  {-0.9, 1.0},
  {-0.8, 1.0},
  {-0.7, 2.0},
  {-0.6, 1.0},
  {-0.5, 5.0},
  {-0.4, 1.0},
  {-0.3, 1.0},
  {-0.2, 3.0},
  {-0.1, 1.0},
  {0.1, 1.0},
  {0.2, 3.0},
  {0.3, 1.0},
  {0.4, 1.0},
  {0.5, 5.0},
  {0.6, 1.0},
  {0.7, 2.0},
  {0.8, 1.0},
  {0.9, 1.0},
  {1.2, 2.0},
  {1.5, 3.0},
  {2.0, 5.0},
  {3.0, 10.0},
  {5.0, 20.0},
  10.0
}

(* }}} *)

(* {{{ VSWRCircle *)

(******************************************************************************)
(* VSWRCircle draws a circle of constant voltage standing wave ratio in a     *)
(* Smith chart.                                                               *)
(******************************************************************************)

Options[VSWRCircle] = {
  LabelPosition      -> Automatic,
  LabelText          -> Automatic,
  PlotStyle          -> GrayLevel[0]
}

VSWRCircle[vswr_, opts___?OptionQ] := DrawVSWRCircle[vswr, opts]

DrawVSWRCircle[vswr_, opts___] :=
  Block[ {r0, vswra, vswrl, lbltxt, lblpos, lblext, pstyle, gprim, error},
    (* Get options. *)
    {lbltxt, pstyle} =
      ResolveOptions[{opts}, VSWRCircle, {LabelText, PlotStyle}];
    {lblpos, error} =
      ResolveAndCheckOptions[{opts}, VSWRCircle, LabelPosition];
    (* Process VSWR specification (may be given in dB). *)
    Which[
      NumberQ[vswr] && vswr > 1.0,
        vswra = vswrl = vswr;
        lblext = "",
      vswrl = vswr/Decibels;
      NumberQ[vswrl] && vswrl > 0.0,
        vswra = 10^N[vswrl/20.0];
        lblext = " dB",
      True,
        Message[VSWRCircle::vswr, vswr];
        Return[{}]
    ];
    (* Compute radius of VSWR circle. *)
    r0 = (vswra - 1.0)/(vswra + 1.0);
    (* Make label. *)
    If[ lbltxt === Automatic,
      lbltxt = "VSWR = " <> ToString[vswrl] <> lblext ];
    If[ lblpos === Automatic,
      lblpos = {0, -r0-0.05} ];
    (* Compose graphics primitives. *)
    If[Head[pstyle] === List, pstyle = Sequence @@ pstyle];
    gprim = {pstyle, Circle[{0, 0}, r0]};
    If[lbltxt =!= None, AppendTo[gprim, Text[lbltxt, lblpos]]];
    (* Return graphics primitives. *)
    gprim
  ]

(* }}} *)

(* {{{ ResistanceCircle, ConductanceCircle, DrawImmittanceCircle *)

(******************************************************************************)
(* ResistanceCircle draws a circle of constant resistance in a Smith chart.   *)
(******************************************************************************)

Options[ResistanceCircle] = {
  LabelText          -> Automatic,
  LabelOffset        -> {0.1, 0.05},
  PointSize          -> 0.02,
  PointColor         -> GrayLevel[0],
  PlotStyle          -> GrayLevel[0],
  ReferenceImpedance -> 50.0
}

ResistanceCircle[Z_, Zref_, opts___?OptionQ] :=
  ResistanceCircle[Z, ReferenceImpedance->Zref, opts]

ResistanceCircle[Z_, opts___?OptionQ] :=
  DrawImmittanceCircle[ResistanceCircle, Z, opts]


(******************************************************************************)
(* ConductanceCircle draws a circle of constant conductance in a Smith chart. *)
(******************************************************************************)

Options[ConductanceCircle] = {
  LabelText          -> Automatic,
  LabelOffset        -> {0.1, 0.05},
  PointSize          -> 0.02,
  PointColor         -> GrayLevel[0],
  PlotStyle          -> GrayLevel[0],
  ReferenceImpedance -> 50.0
}

ConductanceCircle[Z_, Zref_, opts___?OptionQ] :=
  ConductanceCircle[Z, ReferenceImpedance->Zref, opts]

ConductanceCircle[Z_, opts___?OptionQ] :=
  DrawImmittanceCircle[ConductanceCircle, Z, opts]


(******************************************************************************)
(* DrawImmittanceCircle draws a resistance or conductance circle.             *)
(******************************************************************************)

DrawImmittanceCircle[func_, Z_, opts___] :=
  Block[
    {r, re, r0, x0, lbltxt, lbldsp, pstyle, ptsz, ptcol, zref, gprim, error},
    (* Resolve options. *)
    {lbltxt, pstyle, ptcol} =
      ResolveOptions[{opts}, func, {LabelText, PlotStyle, PointColor}];
    {lbldsp, ptsz, zref, error} =
      ResolveAndCheckOptions[ {opts}, func,
        {LabelOffset, PointSize, ReferenceImpedance} ];

    (* Calculate plot coordinates. *)
    r = ReflectionCoefficient[Z, zref];
    If[ func === ResistanceCircle,
      (* Resistance circle. *)
      re = Re @ ReflectionCoefficient[Re[Z], zref];
      r0 = 0.5*(1.0 - re);
      x0 = 0.5*(1.0 + re),
      (* Conductance circle. *)
      re = Re @ ReflectionCoefficient[1.0/Re[1.0/Z], zref];
      r0 = 0.5*(re + 1.0);
      x0 = 0.5*(re - 1.0)
    ];

    (* Compose graphics primitives for point. *)
    If[ lbltxt === Automatic,
      lbltxt = "Zn = " <> ToString[Z/zref] <> "\n r = " <> ToString[r]];
    If[Head[pstyle] === List, pstyle = Sequence @@ pstyle];
    gprim = { pstyle, Circle[{x0, 0}, r0], PointSize[ptsz], 
              ptcol, Point[ReIm[r]] };
    (* Add label. *)
    If[lbltxt =!= None, AppendTo[gprim, Text[lbltxt, ReIm[r] + lbldsp]]];
    (* Return graphics primitives. *)
    gprim
  ]

(* }}} *)

(* {{{ ImpedancePoint, AdmittancePoint, DrawImmittancePoint *)

(******************************************************************************)
(* ImpedancePoint marks the point representing the impedance Z in a           *)
(* Smith chart                                                                *)
(******************************************************************************)

Options[ImpedancePoint] = {
  LabelText          -> Automatic,
  LabelOffset        -> {0.1, 0.1},
  PointSize          -> 0.02,
  PointColor         -> GrayLevel[0],
  ReferenceImpedance -> 50.0
}

ImpedancePoint[Z_, Zref_, opts___?OptionQ] :=
  ImpedancePoint[Z, ReferenceImpedance->Zref, opts]

ImpedancePoint[Z_, opts___?OptionQ] :=
  DrawImmittancePoint[ImpedancePoint, Z, opts]


(******************************************************************************)
(* AdmittancePoint marks the point representing the admittance Y in a         *)
(* Smith chart                                                                *)
(******************************************************************************)

Options[AdmittancePoint] = {
  LabelText          -> Automatic,
  LabelOffset        -> {0.1, 0.1},
  PointSize          -> 0.02,
  PointColor         -> GrayLevel[0],
  ReferenceImpedance -> 50.0
}

AdmittancePoint[Y_, Zref_, opts___?OptionQ] :=
  AdmittancePoint[Y, ReferenceImpedance->Zref, opts]

AdmittancePoint[Y_, opts___?OptionQ] :=
  DrawImmittancePoint[AdmittancePoint, Y, opts]


(******************************************************************************)
(* DrawImmittancePoint draws an impedance or admittance point.                *)
(******************************************************************************)

DrawImmittancePoint[func_, ZY_, opts___] :=
  Block[ {r, lbltxt, lbldsp, ptsz, ptcol, Z, zref, gprim, error},
    (* Resolve options. *)
    {lbltxt, ptcol} = ResolveOptions[{opts}, func, {LabelText, PointColor}];
    {lbldsp, ptsz, zref, error} =
      ResolveAndCheckOptions[ {opts}, func,
        {LabelOffset, PointSize, ReferenceImpedance} ];
    (* Make point label. *)
    If[ func === ImpedancePoint,
      Z = ZY;
      If[ lbltxt === Automatic,
        lbltxt = "Zn = " <> ToString[Z/zref]
                 <> "\n r = " <> ToString @ ReflectionCoefficient[Z, zref]
      ],
      Z = 1.0/ZY;
      If[ lbltxt === Automatic,
        lbltxt = "Yn = " <> ToString[zref/Z] 
                 <> "\n r = " <> ToString @ ReflectionCoefficient[Z, zref]
      ]
    ];
    (* Calculate plot coordinates. *)
    r = ReIm @ ReflectionCoefficient[Z, zref];
    (* Compose graphics primitives for point. *)
    gprim = {PointSize[ptsz], ptcol, Point[r]};
    (* Add label. *)
    If[lbltxt =!= None, AppendTo[gprim, Text[lbltxt, r + lbldsp]]];
    (* Return graphics primitives. *)
    gprim
  ]

(* }}} *)

(* {{{ ImaginaryCircle *)

(******************************************************************************)
(* ImaginaryCircle draws the circular arcs representing trajectories of       *)
(* constant imaginary parts in a Smith chart.                                 *)
(******************************************************************************)

ImaginaryCircle[mode_, y_, stop_:-1] :=
  Block[ {x0, y0, r0, theta1, theta2},
    If[ y != 0,
      (* Calculate the point at which the imaginary circle for y = const. 
        crosses the real circle for x = 0. *)
      {x0, y0} = ReIm @ ReflectionCoefficient[I*y];
      (* Calculate the radius of the imaginary circle for y = const. *)
      r0 = y0-x0*(1.0-x0)/y0;
      (* Draw the part of the imaginary circle which lies inside the
        real circle for x = 0. *)
      If[ y > 0,
        theta1 = PositiveArcTan @@ ({x0, y0} - {1.0, r0});
        theta2 = If[ stop == -1,
          1.5*Pi, 
          PositiveArcTan @@ 
            (ReIm[ReflectionCoefficient[stop + I*y]] - {1.0, r0})
        ];
        If[ mode === Impedance,
          Circle[{1.0, r0}, r0, {theta1, theta2}],
          Circle[{-1.0, r0}, r0, 3.0*Pi-{theta2, theta1}]
        ],
      (* Else *)
        theta1 = If[ stop == -1,
          0.5*Pi,
          PositiveArcTan @@ 
            (ReIm[ReflectionCoefficient[stop + I*y]] - {1.0, r0})          
        ];
        theta2 = PositiveArcTan @@ ({x0, y0} - {1.0, r0});
        If[ mode === Impedance,
          Circle[{1.0, r0}, -r0, {theta1, theta2}],
          Circle[{-1.0, r0}, -r0, Pi-{theta2, theta1}]
        ]
      ],
      (* Special treatment of the case y = 0. *)
      Line[{{-1.0, 0.0}, {1.0, 0.0}}]
    ]
  ]

ImaginaryCircle[mode_, {y_, stop_}] := ImaginaryCircle[mode, y, stop]

(* }}} *)

(* {{{ RealCircle *)

(******************************************************************************)
(* RealCircle draws the circular arcs representing trajectories of constant   *)
(* real parts in a Smith chart.                                               *)
(******************************************************************************)

RealCircle[mode_, x_, stop_:-1] := 
  Block[ {x0, y0, r0, r, theta, arc},
    r = ReflectionCoefficient[x];
    {x0, y0} = 0.5*(ReIm[r] + {1.0, 0.0});
    r0 = 0.5*(1.0 - r);
    theta = If[ stop == -1,
      0.0,
      PositiveArcTan @@
        (ReIm[ReflectionCoefficient[x + I*stop]] - {x0, 0})
    ];
    If[ mode === Impedance,
      arc = {theta, 2.0*Pi-theta},
      arc = {theta + Pi, 3*Pi - theta};
      x0 = -x0
    ];
    Circle[{x0, y0}, r0, arc]
  ]

RealCircle[mode_, {x_, stop_}] := RealCircle[mode, x, stop]

(* }}} *)

(* {{{ ImaginaryCircleLabel *)

(******************************************************************************)
(* ImaginaryCircleLabel draws the axes labels associated with the imaginary   *)
(* circles in a Smith chart.                                                  *)
(******************************************************************************)

ImaginaryCircleLabel[mode_, stretch_, y_] := 
  Block[ {lblpos},
    lblpos = ReIm @ ReflectionCoefficient[I*y];
    If[mode === Admittance, lblpos = -lblpos];
    Text[y, stretch*lblpos]
  ]

ImaginaryCircleLabel[mode_, stretch_, {y_, _}] :=
  ImaginaryCircleLabel[mode, stretch, y]

(* }}} *)

(* {{{ RealCircleLabel *)

(******************************************************************************)
(* RealCircleLabel draws the axes labels associated with the real circles in  *)
(* a Smith chart.                                                             *)
(******************************************************************************)

RealCircleLabel[mode_, disp_, x_] :=
  Block[ {lblpos},
    lblpos = ReIm @ ReflectionCoefficient[x];
    If[mode === Admittance, lblpos = -lblpos];
    Text[x, lblpos + disp]
  ]

RealCircleLabel[mode_, disp_, {x_, _}] :=
  RealCircleLabel[mode, disp, x]

(* }}} *)

(* {{{ ReAndIm, ReflectionReAndIm *)

(******************************************************************************)
(* Given a list of sampling points, ReAndIm computes the real and imaginary   *)
(* parts of the values of a list of transfer functions.                       *)
(******************************************************************************)

SetAttributes[ReAndIm, HoldAll]

ReAndIm[tf_, var_, pts_] :=
  With[ {values = Transpose @ Map[ReIm, Map[(var = #; tf)&, pts], {2}]},
    var = .;
    values
  ]

(******************************************************************************)
(* Given a list of sampling points, ReflectionReAndIm computes the real and   *)
(* imaginary parts of the reflection factors associated with the values in a  *)
(* list of impedance functions.                                               *)
(******************************************************************************)

SetAttributes[ReflectionReAndIm, HoldAll]

ReflectionReAndIm[tf_, var_, Zref_, pts_] :=
  With[
    { values = Transpose @
        Map[ReIm, Map[(var = #; ReflectionCoefficient[tf, Zref])&, pts], {2}] },
    var = .;
    values
  ]

(* }}} *)

(* {{{ SmithChart *)

(******************************************************************************)
(* Options for SmithChart                                                     *)
(******************************************************************************)

SetAttributes[{SmithChart, ShowSmithChart}, HoldFirst]

Options[SmithChart] =  Join[ 
  Options[ListPlot], Options[Legend],
  { 
    ChartStyle         -> Impedance,
    DataConversion     -> True,
    Decorations        -> None,
    FrequencyScaling   -> Exponential,
    GridResolution     -> Medium,
    LabelOffset        -> {0.05, -0.03},
    LabelStretchFactor -> 1.05,
    MajorGridStyle     -> {Thickness[0.004], GrayLevel[0.5]},
    MinorGridStyle     -> GrayLevel[0.7],
    PlotPoints         -> 25,
    ReferenceImpedance -> 50.0,
    ShowLegend         -> True,
    TraceNames         -> Automatic
  }
]

SetOptions[ SmithChart,
  AspectRatio       -> 1.0,
  Axes              -> False,
  LegendOrientation -> Horizontal,
  LegendPosition    -> {-0.8, -1.3},
  LegendSize        -> {1.7, 0.2},
  PlotJoined        -> True,
  PlotRange         -> {{-1.1, 1.1}, {-1.1, 1.1}},
  PlotStyle         -> {{RGBColor[1., 0., 0.], Thickness[0.005],
                         PointSize[0.02]},
                        {RGBColor[0., .8, 0.], Thickness[0.005],
                         Dashing[{0.03, 0.02}], PointSize[0.02]},
                        {RGBColor[0., 0., 1.], Thickness[0.005],
                         Dashing[{0.03, 0.02, 0.01, 0.02}], PointSize[0.02]},
                        {RGBColor[0., 1., 1.], Thickness[0.005],
                         Dashing[{0.01, 0.01}], PointSize[0.02]}}
]

(******************************************************************************)
(* SmithChart plots impedances or admittances in a Smith chart.               *)
(******************************************************************************)

SmithChart[ tf:{{((_Symbol | _String) -> _)..}},
  {var_Symbol, fStart_?Positive, fEnd_?Positive}, opts___?OptionQ ] :=
  With[ {ipfuns = Cases[First[tf], Literal[_->_InterpolatingFunction]]},
    ShowSmithChart[ #, {var, fStart, fEnd}, opts, TraceNames->
      Replace[TraceNames /. Options[SmithChart], Automatic->(First /@ ipfuns)]
    ]& @ Map[Replace[#, (_ -> ipf_) :> ipf[var]]&, ipfuns]
  ]

SmithChart[ tf:{{((_Symbol | _String) -> _)..}}, traces_,
  {var_Symbol, fStart_?Positive, fEnd_?Positive}, opts___?OptionQ ] :=
  Block[ {tracelst, ipfuns},
    tracelst = If[Head[traces] === List, traces, {traces}];
    ipfuns = Cases[First[tf], Literal[_->_InterpolatingFunction]];
    ShowSmithChart[ #, {var, fStart, fEnd}, opts, TraceNames->
      Replace[TraceNames /. Options[SmithChart], Automatic->tracelst] ]& @
      (tracelst /. ipfuns)
  ]
         
SmithChart[ tf:{__}, {var_Symbol, fStart_?Positive, fEnd_?Positive},
             opts___?OptionQ ] :=
  ShowSmithChart[tf, {var, fStart, fEnd}, opts]    

SmithChart[tf_Symbol, args___] := 
  SmithChart[Evaluate[tf], args] /; Head[tf] =!= Symbol

(* For plotting a single trace. *)
SmithChart[ tf_?(Head[#] =!= List &),
             {var_Symbol, fStart_?Positive, fEnd_?Positive},
             opts___?OptionQ ] :=
  ShowSmithChart[{tf}, {var, fStart, fEnd}, opts]

(* For displaying only the grid. *)
SmithChart[opts___?OptionQ] :=
  ShowSmithChart[Null, {Null, Null, Null}, opts]
  
ShowSmithChart[tf_, {var_, fStart_, fEnd_}, opts___] :=
  Block[
    { plotpts, prange, pstyle, scaling, localopts, tfvals, fpts, fs, fe,
      plots, imgsize, dspfun, error, legend, legendargs, trnames,
      ntraces, curopts, showcmd, Zref, mode, smithgrid, majorgridstyle, 
      minorgridstyle, gridres, lblstretch, lbldisp, dataconv, decorations },

    (* Retrieve and check option values. *)
    {scaling, mode, gridres, lbldisp, lblstretch, Zref, dataconv, error} =
      ResolveAndCheckOptions[ {opts}, SmithChart,
        { FrequencyScaling, ChartStyle, GridResolution, LabelOffset,
          LabelStretchFactor, ReferenceImpedance, DataConversion } ];
    If[error, Return[$Failed]];
    { dspfun, imgsize, plotpts, prange, pstyle, legend, trnames,
      majorgridstyle, minorgridstyle, decorations } =
      ResolveOptions[ {opts}, SmithChart,
        { DisplayFunction, ImageSize, PlotPoints, PlotRange, PlotStyle,
          ShowLegend, TraceNames, MajorGridStyle, MinorGridStyle,
          Decorations } ];

    If[ tf =!= Null,
      (* Check if all function yield numerical results. *)
      If[ MemberQ[NumberQ /@
                  Flatten[ReflectionReAndIm[tf, var, Zref, {fStart}]], False],
        Message[SmithChart::nres];
        Return[$Failed] ];

      (* Check frequency interval. *)
      If[ fStart < fEnd,
        fs = N[fStart]; fe = N[fEnd],
        fs = N[fEnd];   fe = N[fStart] ];

      If[ fs == fe,
        Message[SmithChart::frng];
        Return[$Failed] ];

      (* More than 2 PlotPoints? *)
      If[ !IntegerQ[plotpts] || plotpts < 2,
        Message[SmithChart::ppts, plotpts = 25] ];

      (* Set up a list of frequency points. *)
      fpts = SamplingPoints[scaling, fs, fe, plotpts];

      (* Compute values of transfer functions at sampling points. *)
      tfvals = If[ dataconv,
        ReflectionReAndIm[tf, var, Zref, fpts],
        ReAndIm[tf, var, fpts]
      ];

      (* Make cyclic list of plot styles. *)
      ntraces = Length[First @ Map[Hold, Hold[tf], {2}]];
      pstyle =  Replace[#, {Automatic} -> Automatic]& /@ 
                  CycleOptions[pstyle, ntraces],

      (* Else *)
      ntraces = 0
    ];
      
    (* Set options for use with ListPlot. *)
    curopts = CurrentOptions[SmithChart, opts];
    localopts = {
      FilterOptionsEx[ ListPlot,
        {DisplayFunction, ImageSize, PlotRange, PlotStyle}, curopts ],
      DisplayFunction -> Identity };

    (* Write list of decorations in canonical form. *)
    If[ decorations === None,
      decorations = {},
      If[Head[decorations] =!= List, decorations = {decorations}]
    ];

    (* Make Smith chart grid and labels. *)
    If[ Head[minorgridstyle] === List,
      minorgridstyle = Sequence @@ minorgridstyle ];
    If[ Head[majorgridstyle] === List,
      majorgridstyle = Sequence @@ majorgridstyle ];
    smithgrid = Graphics[
      {
        (* Draw minor grid lines. *)
        minorgridstyle,
        RealCircle[mode, #]& /@ MinorRealCircles[gridres],
        ImaginaryCircle[mode, #]& /@ MinorImaginaryCircles[gridres],
        (* Draw major grid lines on top of minor ones. *)
        majorgridstyle,
        RealCircle[mode, #]& /@ MajorRealCircles[gridres],
        ImaginaryCircle[mode, #]& /@ MajorImaginaryCircles[gridres],
        (* Draw origin. *)
        GrayLevel[1.0], Disk[{0, 0}, 0.02],
        majorgridstyle, Circle[{0, 0}, 0.02],
        (* Draw axes labels. *)
        GrayLevel[0.0],
        RealCircleLabel[mode, lbldisp, #]& /@ MinorRealCircles[gridres],
        ImaginaryCircleLabel[mode, lblstretch, #]& /@
          MinorImaginaryCircles[gridres],
        RealCircleLabel[mode, lbldisp, #]& /@ MajorRealCircles[gridres],
        ImaginaryCircleLabel[mode, lblstretch, #]& /@ 
          MajorImaginaryCircles[gridres]
      }
    ];

    plots = If[ tf =!= Null,
      (* Make Smith charts from list of plot points. *)
      MapThread[
        Show[
          { ListPlot[#1, Sequence @@ localopts, PlotStyle -> #2], 
            Graphics[
              { If[Head[#2] === List, Sequence @@ #2, #2],
                Point @ #1[[1]] }
            ] 
          } 
        ]&,
        {tfvals, pstyle}
      ],
      {}
    ];

    (* Turn off legend display if no trace names are given. *)
    If[ trnames === Automatic,
      legend = False,
      If[Head[trnames] =!= List, trnames = {trnames}]
    ];

    (* Check if there is a name for each trace. *)
    If[ legend === True && Length[trnames] != ntraces,
      Message[SmithChart::ntrn];
      legend = False ];

    (* Compose legend. *)
    If[ legend === True,
      showcmd = ShowLegend;
      legendargs = {{
        Transpose[ {
          Graphics[
            Append[If[# === Automatic, {}, #], Line[{{0, 0.5}, {1, 0.5}}]]
          ]& /@ pstyle, 
          trnames } 
        ], 
        FilterOptionsEx[Legend, {}, curopts]
      }},

      showcmd = Show;
      legendargs = {}
    ];

    (* Display the chart. *)
    showcmd[
      Show[
        Join[{smithgrid}, plots, {Graphics[decorations]}],
        FilterOptionsEx[Graphics, {DisplayFunction}, curopts],
        DisplayFunction->Identity
      ],
      Sequence @@ legendargs,
      ImageSize->imgsize, DisplayFunction->dspfun
    ]
  ]

(* }}} *)

End[]

(* {{{ Protection of exported symbols *)

Protect @@ `Private`$ExportedProtectedSymbols;
Remove[`Private`$ExportedProtectedSymbols];

(* }}} *)

EndPackage[]

(*! Local Variables *)
(* local Variables: *)
(* fill-column: 79 *)
(* comment-column: 20 *)
(* End: *)
