Link to home
Start Free TrialLog in
Avatar of gagsd
gagsdFlag for India

asked on

Convert C++ to Excel VBA

I have a code in C++ which needs to be converted into VBA.

//#include <Rcpp.h>
//#include <math.h>
//#include <iostream.h>

using namespace Rcpp;
double qnormal_x,qnormal_y,qnormal_z;
double expq(double q, double w){
if(q==1.0){
return(exp(w));
}
else{
return (expl(log(1.0+(1.0-q)*w)/(1.0-q)));
}
}
double lnq(double q, double w){
if(q==1.0){
return(log(w));
}
else{
return ((exp(log(w)*(1.0-q))-1.0)/(1.0-q));
}
}
void setseed_qnormal(double v0, double z0){
qnormal_x = sqrt(1-v0*v0);
qnormal_y = v0;
qnormal_z = z0;
}
double Q8(double w, double v){
return(8*w*v*(((16.0*w*w-24.0)*w*w+10.0)*w*w-1.0));
}
double P8(double w){
return((((128.0*w*w-256.0)*w*w+160.0)*w*w-32.0)*w*w+1.0);
}
double f(double z){
return(1.0-fabs(1.0-1.99999*z));
}
void qnormal(double q){
double qq;
qnormal_y = Q8(qnormal_x,qnormal_y);
qnormal_x = P8(qnormal_x);
qq = (q+1.0)/(3.0-q);
qnormal_z = f(expq(qq,-qnormal_z*qnormal_z*0.5));
qnormal_z = sqrt(-2.0*lnq(qq,qnormal_z));
}  

// [[Rcpp::export]]
NumericVector Chaotic(int n,double q,double v0, double z0){
  double eta;
  double xi;
  NumericVector ux(n);
setseed_qnormal(v0,z0);
for(int i=0;i<n;i++){
qnormal(q);
xi = qnormal_x*qnormal_z;
eta = qnormal_y*qnormal_z;
ux[i]= xi;
}
return ux;
}

Open in new window

Avatar of ste5an
ste5an
Flag of Germany image

What have you so far?

btw, what is your use-case? Cause making this a library could be the better way.
Avatar of gagsd

ASKER

I tried converting it as follows:

Public Function Chaotic(Optional ByVal q As Double = 1#) As Double

    If q > 3 Or q = 3 Then Exit Function

    Dim u1 As Double, u2 As Double, qq As Double, z As Double
    Dim qnormal_x As Double, qnormal_y As Double, qnormal_z As Double

    u1 = Application.WorksheetFunction.NormInv(MT19937_RealNum3#(), 0, 1)    'v0
    u2 = Application.WorksheetFunction.NormInv(MT19937_RealNum3#(), 0, 1)    'z0

    qnormal_x = Sgn((1 - u1 * u1)) * Sqr(Abs(1 - u1 * u1))
    qnormal_y = u1
    qnormal_z = u2

    qnormal_y = (8 * qnormal_x * qnormal_y * (((16 * qnormal_x * qnormal_x - 24) * qnormal_x * qnormal_x + 10) * qnormal_x * qnormal_x - 1))
    qnormal_x = ((((128 * qnormal_x * qnormal_x - 256) * qnormal_x * qnormal_x + 160) * qnormal_x * qnormal_x - 32) * qnormal_x * qnormal_x + 1)
    qq = (1 + q) / (3 - q)
    qnormal_z = Sqr(-2 * qLOG(qq, (1 - Abs(1 - 1.99999 * (qEXP(qq, -qnormal_z * qnormal_z * 0.5))))))

    Chaotic = qnormal_y * qnormal_z

End Function
Private Function qEXP(ByVal q As Double, ByVal w As Double) As Double
    If q = 1 Then
        qEXP = (Exp(w))
    Else
        qEXP = (Exp(Log(1 + (1 - q) * w) / (1 - q)))
    End If
End Function
Private Function qLOG(ByVal q As Double, ByVal w As Double) As Double
    If q = 1 Then
        qLOG = (Log(w))
    Else
        qLOG = ((Exp(Log(w) * (1 - q)) - 1) / (1 - q))
End If
End Function

Open in new window


But, I am not getting the desired results. For e.g., for q=1.5 and with 10,000 iterations, it should generate a dataset with mean=0 and standard deviation=1.73. Reference .
Avatar of gagsd

ASKER

We can use RND() in place of MT19937_RealNum3#(). MT19937_RealNum3# is a separate function which is equivalent to RND() of VBA.
You did too much for the first step. Here we do just a 1:1 structural "translation". Thus every function and method in the original source gets its VBA counter-part.
Cause there are no such variables like u1, u2 in the original source.

The problem is: You cannot test the single functions and compare them right now. So start by translating this:

double qnormal_x,qnormal_y,qnormal_z;

void setseed_qnormal(double v0, double z0){qnormal_x = sqrt(1-v0*v0);qnormal_y = v0;qnormal_z = z0;}

double expq(double q, double w){if(q==1.0){return(exp(w));}else{return (expl(log(1.0+(1.0-q)*w)/(1.0-q)));}}
double lnq(double q, double w){if(q==1.0){return(log(w));}else{return ((exp(log(w)*(1.0-q))-1.0)/(1.0-q));}}

double Q8(double w, double v){return(8*w*v*(((16.0*w*w-24.0)*w*w+10.0)*w*w-1.0));}
double P8(double w){return((((128.0*w*w-256.0)*w*w+160.0)*w*w-32.0)*w*w+1.0);}
double f(double z){return(1.0-fabs(1.0-1.99999*z));}

void qnormal(double q){
	double qq;
	qnormal_y = Q8(qnormal_x,qnormal_y);
	qnormal_x = P8(qnormal_x);
	qq = (q+1.0)/(3.0-q);
	qnormal_z = f(expq(qq,-qnormal_z*qnormal_z*0.5));
	qnormal_z = sqrt(-2.0*lnq(qq,qnormal_z));
}  

NumericVector Chaotic(int n,double q,double v0, double z0){
	double eta;
	double xi;
	NumericVector ux(n);
	setseed_qnormal(v0,z0);
	for(int i=0;i<n;i++){
		qnormal(q);
		xi = qnormal_x*qnormal_z;
		eta = qnormal_y*qnormal_z;
		ux[i]= xi;
	}

	return ux;
}

Open in new window


Write one function/method for each one in the above source, don't collapse/compact function calling. Cause this makes it harder to see, where your code works or behaves different. E.g. rounding could be different due to this.

Then you may wrap it up in an consuming function, where you pass-in some (randomized) values.
btw, using a different pseudo number generator than the built-in requires that you use a trustable one. Cause writing such a generator is pretty hard.
Avatar of gagsd

ASKER

I did that as well. But, the problem is C++ executes differently and I could not execute the last part on a standalone basis.

NumericVector Chaotic(int n,double q,double v0, double z0){
	double eta;
	double xi;
	NumericVector ux(n);
	setseed_qnormal(v0,z0);
	for(int i=0;i<n;i++){
		qnormal(q);
		xi = qnormal_x*qnormal_z;
		eta = qnormal_y*qnormal_z;
		ux[i]= xi;
	}

	return ux;
}

Open in new window


As per C++ qnormal(q) results in qnormal_x, qnormal_y, and qnormal_z. But when you call qnormal(q) in VBA it generates only one output. Hence I had to combine them. No other option.
Read the source again: qnormal does not have any output. It modifies global variables. Just do the same in your VBA code..

Option Explicit

Private qnormal_x As Double
Private qnormal_y As Double
Private qnormal_z As Double

Private Sub setseed_qnormal(v0 As Double, z0 As Double)
  qnormal_x = Math.Sqr(1 - v0 * v0)
  qnormal_y = v0
  qnormal_z = z0
End Sub

Private Function expq(q As Double, w As Double) As Double
  If (q = 1#) Then
    expq = Math.Exp(w)
  Else
    expq = Math.Exp(Log(1# + (1# - q) * w) / (1# - q)) ' Was expl, check precision.
  End If
End Function

Private Function lnq(q As Double, w As Double) As Double
  If (q = 1#) Then
    lnq = Log(w)
  Else
    lnq = (Exp(Log(w) * (1# - q)) - 1#) / (1# - q)
  End If
End Function

Private Function Q8(w As Double, v As Double) As Double
  Q8 = (8# * w * v * (((16# * w * w - 24#) * w * w + 10#) * w * w - 1#))
End Function

Private Function P8(w As Double) As Double
  P8 = ((((128# * w * w - 256#) * w * w + 160#) * w * w - 32#) * w * w + 1#)
End Function

Private Function f(z As Double) As Double
  f = (1# - Math.Abs(1# - 1.99999 * z)) ' Was fabs, check precision.
End Function

Private Sub qnormal(q As Double)
  Dim qq As Double
 
  qnormal_y = Q8(qnormal_x, qnormal_y)
  qnormal_x = P8(qnormal_x)
  qq = (q + 1#) / (3# - q)
  qnormal_z = f(expq(qq, -qnormal_z * qnormal_z * 0.5))
  qnormal_z = Math.Sqr(-2# * lnq(qq, qnormal_z))
End Sub

Public Function Chaotic(n As Long, q As Double, v0 As Double, z0 As Double) As Double()
  Dim eta As Double
  Dim xi As Double
  Dim ux() As Double
  Dim i As Long
  
  ReDim ux(0 To n - 1)
  setseed_qnormal v0, z0
  For i = 0 To n - 1
    qnormal q
    xi = qnormal_x * qnormal_z
    eta = qnormal_y * qnormal_z
    ux(i) = xi
  Next i

  Chaotic = ux
End Function

Open in new window


p.s. eta in the final method is not used.
Well, I would start by fixing the C++ source code. so many global variables that you don't really need ....

Next, identify what can be translated:
C Structures becomes user defined Types.
Void Functions becomes procédures (Sub)
Function returning value becomes function.
C++ structures and classes becomes classes.
References or pointers parameters becomes ByRef parameters.

Keep in mind that VBA support some objects concepts, such as interface inheritance.
With some efforts, you can even translate STL functionalities.
My only rewrite would be avoiding globals and using a type instead of three separate variables:

Option Explicit

Private Type VectorType
  X As Double
  Y As Double
  Z As Double
End Type

Public Function Chaotic(n As Long, q As Double, v0 As Double, z0 As Double) As Double()

  Dim qn As VectorType
  Dim ux() As Double
  Dim i As Long

  ReDim ux(0 To n - 1)
  SeedVector qn, v0, z0
  For i = 0 To n - 1
    CalculateVector qn, q
    ux(i) = qn.X * qn.Z
  Next i

  Chaotic = ux

End Function

Private Sub CalculateVector(ByRef vector As VectorType, q As Double)

  Dim qq As Double

  vector.Y = Q8(vector.X, vector.Y)
  vector.X = P8(vector.X)
  qq = (q + 1#) / (3# - q)
  vector.Z = F(ExpQ(qq, -vector.Z * vector.Z * 0.5))
  vector.Z = Math.Sqr(-2# * LnQ(qq, vector.Z))

End Sub

Private Sub SeedVector(ByRef vector As VectorType, v0 As Double, z0 As Double)

  vector.X = Math.Sqr(1 - v0 * v0)
  vector.Y = v0
  vector.Z = z0

End Sub

Private Function F(Z As Double) As Double

  F = (1# - Math.Abs(1# - 1.99999 * Z)) ' Was fabs, check precision.

End Function

Private Function ExpQ(q As Double, w As Double) As Double

  If (q = 1#) Then
    ExpQ = Math.Exp(w)
  Else
    ExpQ = Math.Exp(Log(1# + (1# - q) * w) / (1# - q)) ' Was expl, check precision.
  End If

End Function

Private Function LnQ(q As Double, w As Double) As Double

  If (q = 1#) Then
    LnQ = Log(w)
  Else
    LnQ = (Exp(Log(w) * (1# - q)) - 1#) / (1# - q)
  End If

End Function

Private Function P8(w As Double) As Double

  P8 = ((((128# * w * w - 256#) * w * w + 160#) * w * w - 32#) * w * w + 1#)

End Function

Private Function Q8(w As Double, v As Double) As Double

  Q8 = (8# * w * v * (((16# * w * w - 24#) * w * w + 10#) * w * w - 1#))

End Function

Open in new window

Avatar of gagsd

ASKER

@ste5an,

I think I am missing something over there.

N in the original C++ code is the number of iterations (data items to be generated) and hence they would be part of a SUB in VBA and not of the function (Am I wrong here??).

Also, the output data being generated does not conform to the mean and stdev of the expected distribution  as per the wikipedia page.

For, q=1.5 the mean = 0 and stdev=1.73.

Also, if I give a static input for v0 and z0 then my output has the same values.
Public Function Chaotic(n As Long, q As Double, v0 As Double, z0 As Double) As Double()

Open in new window


What is the output mean and stdev. of the data (N=10000) when you are running it on C++ and VBA. I do not have a C++ compiler so not in a position to run C code.
N in the original C++ code is the number of iterations (data items to be generated) and hence they would be part of a SUB in VBA and not of the function (Am I wrong here??).

- There is only one "data item" generated (returned), a NumericVector.
- There is a loop to generate a N-dimensional vector, but not an "iteration". Different concepts, even when both may use for() loops in code.

Also, the output data being generated does not conform to the mean and stdev of the expected distribution  as per the wikipedia page.
D'oh? Where does that come from? What to you mean (pun intended)? The result of that calculation is a single vector. Mean and deviation are statistical measures without sense in this case.

For, q=1.5 the mean = 0 and stdev=1.73.
Also, if I give a static input for v0 and z0 then my output has the same values.
What do you mean ??

Simple tests in C++ in comparison to VBA show no difference. E.g.

Public Sub Test()

  Dim v() As Double
  
  v = Chaotic(3, 10, 0, 0)
  Debug.Print v(2)

End Sub

Open in new window


returns ~0,881917 in both systems.
Avatar of gagsd

ASKER

See, this code is to generate Tsallis q-Gaussian Random Data. If we run 10,000 iterations of this the output is 10,000 different random numbers. Say if we output these numbers in Col. A, then for Range("A1:A10000") the mean and stdev. should match the mean and stdev as per the wikipedia page. For eg. for q=1.5 the mean=0 and stdev=(1 / ((1 / (3 - q)) * (5 - 3 * q))) ^ (1 / 2)=1.732050.

Hope this clarifies further.
Well, the problem is quite simple: The original code has no random function. So either you omitted the important parts or you posted the wrong source.
This question needs an answer!
Become an EE member today
7 DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform.
View membership options
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.