# How to use FindFit to optimize parameters of a WSM model via WSMLink for a parametric curve

I have I-V (current-voltage) data which I am trying to use to calibrate a PV solar cell model in WSM using WSMLink from mathematica. I am using the FindFit function which simulates the model with each iteration. The code below is my best attempt but may have several issues. Thanks in Advance!

IVdata which I am trying to fit is:

IVdata={{0.1035, 0.5633}, {0.212, 0.5521}, {0.3165, 0.5398}, {0.413,
0.5265}, {0.499, 0.5119}, {0.573, 0.496}, {0.632, 0.4784}, {0.6755,
0.459}, {0.7065, 0.4373}, {0.728, 0.4137}, {0.7385,
0.3873}, {0.7465, 0.3585}, {0.7505, 0.3269}, {0.754,
0.2924}, {0.7555, 0.2545}, {0.757, 0.1678}, {0.757, 0.2132}, {0.759,
0.1185}, {0.76, 0.0646}, {0.7605, -0.0588}, {0.7605,
0.0057}, {0.762, -0.1291}, {0.764, -0.2057}};


I can run the simulation with the default parameter values and plot it against IVdata:

Needs["WSMLink"]
initResults = WSMSimulate["Renewable_Systems.TestSolarCell"]

currOut = initResults["powerSensor.pc.i"];
voltOut = initResults["powerSensor.pc.v"];

dataPlot = ListPlot[IVdata];
modelPlot =
ParametricPlot[{voltOut[t], currOut[t]}, {t, 0.1, 0.5},
AspectRatio -> 1, PlotRange -> Full];

Show[dataPlot, modelPlot, PlotRange -> All]


And the curve seems to have the right shape to fit the data.

I have been using an example of model calibration for a DC motor in the notebook from the following video to guide me: Wolfram SystemModeler: Quick Start with Mathematica

The difference is that I want my code to fit the data to a parametric plot (I vs V) rather than a plot in the time domain as the example does.

So this is my attempt to do so:

The following two functions are helper functions which simulate the model and retrieve the variable value and gradient in the parameter directions (5 paramters).

modelFunc[p1Name_, p1Val_?NumericQ, p2Name_, p2Val_?NumericQ, p3Name_,
p3Val_?NumericQ, p4Name_, p4Val_?NumericQ, p5Name_,
p5Val_?NumericQ] :=
modelFunc[p1Name, p1Val, p2Name, p2Val, p3Name, p3Val, p4Name,
p4Val, p5Name, p5Val] =
WSMSimulate["Renewable_Systems.TestSolarCell",
WSMParameterValues -> {p1Name -> p1Val, p2Name -> p2Val,
p3Name -> p3Val, p4Name -> p4Val,
p5Name -> p5Val}][{"powerSensor.pc.i"}][[1]];

p3Name_, p3Val_?NumericQ, p4Name_, p4Val_?NumericQ, p5Name_,
p5Val_?NumericQ, p6Name_, p6Val_?NumericQ] :=
modelFuncGrad[p1Name, p1Val, p2Name, p2Val, p3Name, p3Val, p4Name,
p4Val, p5Name, p5Val, p6Name, p6Val] =
WSMSimulateSensitivity[
"Renewable_Systems.TestSolarCell", {p1Name, p2Name, p3Name,
p4Name, p5Name, p6Name},
WSMParameterValues -> {p1Name -> p1Val, p2Name -> p2Val,
p3Name -> p3Val, p4Name -> p4Val, p5Name -> p5Val,
p6Name -> p6Val}][{"powerSensor.pc.i(" <> p1Name <> "," <>
p2Name <> "," <> p2Name <> "," <> p4Name <> "," <> p5Name <>
"," <> p6Name <> ")"}][[1]];


The following block uses FindFit and the helper functions to optimize the model parameters to fit IVdata.

fittedPars = FindFit[IVdata,modelFunc[Iph,I0,n,Rs,Rp]["powerSensor.pc.v"],
{{Iph, 1},{I0, 0.0000005},{n, 1.5}, {Rs,0.1}, {Rp, 50}}, Vout,Gradient :>


Running the code gives the following errors:

FindFit::nrlnum: The function value {-0.5633+{powerSensor.pc.i}[powerSensor.pc.v],-0.5521+{powerSensor.pc.i}[powerSensor.pc.v],-0.5398+{powerSensor.pc.i}[powerSensor.pc.v],<<18>>,0.1291 +{powerSensor.pc.i}[powerSensor.pc.v],0.2057 +{powerSensor.pc.i}[powerSensor.pc.v]} is not a list of real numbers with dimensions {23} at {Iph,I0,n,Rs,Rp} = {1.,5.*10^-7,1.5,0.1,50.}.

FindFit::nrlnum: The function value {-0.5633+{powerSensor.pc.i}[powerSensor.pc.v],-0.5521+{powerSensor.pc.i}[powerSensor.pc.v],-0.5398+{powerSensor.pc.i}[powerSensor.pc.v],<<18>>,0.1291 +{powerSensor.pc.i}[powerSensor.pc.v],0.2057 +{powerSensor.pc.i}[powerSensor.pc.v]} is not a list of real numbers with dimensions {23} at {Iph,I0,n,Rs,Rp} = {1.,5.*10^-7,1.5,0.1,50.}.


I'm not sure how I can post the WSM simulation for your reference so I apologise for that.

So my question is, how do I syntax the helper functions and FindFit function to achieve a model fit of a parametric curve?

I'll use a model of a ChuaCircuit shipped with SystemModeler in this answer, to make results reproducible and be able to test my code. You should be able to adjust the code to your specific problem.

Start off by declaring a function for simulating the model and extracting the two parametric variables:

ClearAll[modelFunc];
modelFunc[p1Name_, p1Val_?NumericQ, p2Name_, p2Val_?NumericQ] :=
modelFunc[p1Name, p1Val, p2Name, p2Val] =
WSMSimulate["ChuaCircuit",
WSMParameterValues -> {
p1Name -> p1Val,
p2Name -> p2Val
}][{"C1.v", "C2.v"}];


Run it for a given parameter set:

{x, y} = modelFunc["C1.C", 10, "C2.C", 100];


Generate some data to fit against by randomly shifting sampled data from a real simulation:

randomData = Table[
{t, {RandomReal[{0.97, 1.03}]*x[t], RandomReal[{0.97, 1.03}]*y[t]}},
{t, 0, 200, 10}
]


Show the data together with the simulation:

Show[
ListPlot[randomData[[All, 2]]],
ParametricPlot[{x[t], y[t]}, {t, 0, 200}]
]


Create a function that simulates and evaluates the variables at a given time point:

ClearAll[modelFuncAtT];
modelFuncAtT[t_, p1Name_, p1Val_?NumericQ, p2Name_, p2Val_?NumericQ] :=
Module[{x, y},
{x, y} = modelFunc[p1Name, p1Val, p2Name, p2Val];
{x[t], y[t]}
]


Use FindMinimum with a 2-norm as a fitting function:

res = FindMinimum[
Norm[
Map[modelFuncAtT[#[[1]], "C1.C", c1c, "C2.C", c2c] - #[[2]] &, randomData],
2
],
{{c1c, 5}, {c2c, 101}},
StepMonitor :> Print[{c1c, c2c}],
AccuracyGoal -> 4,
PrecisionGoal -> 4
]


Simulate once for the found fit:

{x, y} = modelFunc["C1.C", c1c, "C2.C", c2c] /. res[[2]];


Plot the resulting fit with the data:

Show[
ListPlot[randomData[[All, 2]]],
ParametricPlot[{x[t], y[t]}, {t, 0, 200}]
]
`