7
\$\begingroup\$

I'm working on making an iOS app to solve physics problems and I'm starting by just having it solve basic kinematics problems with the following formulas (I've coded it in C instead of Swift to start because I know C better):

  • Time: \$t\$
  • Change in Distance: \$d\$
  • Average Velocity: \$v_a\$
  • Initial Velocity: \$v_i\$
  • Final Velocity: \$v_f\$
  • Acceleration: \$a\$
  • \$v_a = \frac{v_f + v_i}{2}\$
  • \$v_f^2 = v_i^2 + 2ad\$
  • \$d = v_it + \frac{1}{2}at^2\$
  • \$a = \dfrac{v_f - v_i}{t}\$
  • \$v_a = \frac{d}{t}\$

I'm using the following code to solve for as many of the variables as possible once the given variables have been input. The array being passed in contains the variables I showed above in the same order. The boolean values such as tChanged or vaChanged are initialized as false and then changed to true if the user inputs a value for them or if my algorithm assigns them a value:

void calculateUnknowns1(double values[6])
    {
    int i = 0;

    do{
        i = 0;

    /*-------------------------------------------DERIVED FROM: va = (vf+vi)/2----------------------------------------------------*/

            if((vfChanged)&&(viChanged)){
                if(!vaChanged){
                    values[2] = (values[4]+values[3]) / 2;
                    i++;
                    vaChanged = true;
                }
            }
            if((vaChanged)&&(viChanged)){
                if(!vfChanged){
                    values[4] = 2*values[2] - values[3];
                    i++;
                    vfChanged = true;
                }
            }
            if((vaChanged)&&(vfChanged)){
                if(!viChanged){
                    values[3] = 2*values[2] - values[4];
                    i++;
                    viChanged = true;
                }
            }


/*----------------------------------------DERIVED FROM: vf*vf = vi*vi + 2*a*d------------------------------------------------*/

            if((viChanged)&&(aChanged)&&(dChanged)){
                if(!vfChanged){
                    values[4] = sqrtl(values[3]*values[3] + 2*values[5]*values[1]);
                    i++;
                    vfChanged = true;
                }
            }
            if((vfChanged)&&(aChanged)&&(dChanged)){
                if(!viChanged){
                    values[3] = sqrtl(values[4]*values[4] - 2*values[5]*values[1]);
                    i++;
                    viChanged = true;
                }
            }
            if((viChanged)&&(vfChanged)&&(aChanged)){
                if(!dChanged){
                    values[1] = (values[4]*values[4] - values[3]*values[3]) / (2*values[5]);
                    i++;
                    dChanged = true;
                }
            }
            if((viChanged)&&(vfChanged)&&(dChanged)){
                if(!aChanged){
                    values[5] = (values[4]*values[4] - values[3]*values[3]) / (2*values[1]);
                    i++;
                    aChanged = true;
                }
            }


/*-------------------------------------------DERIVED FROM: d = vit+1/2at*t---------------------------------------------------*/

            /*if((tChanged)&&(dChanged)&&(values[3]==0)){
                if(!aChanged){
                    values[5] = values[1]/(0.5*values[0]*values[0]);
                    i++;
                    aChanged = true;                                      EXTRANNEOUS
                }
            }
            if((tChanged)&&(dChanged)&&(values[5]==0)){
                if(!viChanged){
                    values[3] = values[1]/values[0];
                    i++;
                    viChanged = true;
                }
            }*/
            if((tChanged)&&(viChanged)&&(aChanged)){
                if(!dChanged){
                    values[1] = values[3]*values[0] + 0.5*values[5]*values[0]*values[0];
                    i++;
                    dChanged = true;
                }
            }
            if((tChanged)&&(dChanged)&&(aChanged)){
                if(!viChanged){
                    values[3] = (values[1] - 0.5*values[5]*values[0]*values[0]) / values[0];
                    i++;
                    viChanged = true;
                }
            }
            if((tChanged)&&(dChanged)&&(viChanged)){
                if(!aChanged){
                    values[5] = (values[1] - values[3]*values[0]) / (0.5*values[0]*values[0]);
                    i++;
                    aChanged = true;
                }
            }
            if((dChanged)&&(viChanged)&&(aChanged)){
                if(!tChanged){
                    values[0] = (-(values[3]) + sqrtl(values[3]*values[3] - 4*0.5*values[5]*(-values[1]))) / values[5];
                    if(!((values[1] == values[3]*values[0] + 0.5*values[5]*values[0]*values[0])&&(values[0]>=0))){
                        values[0] = (-(values[3]) - sqrtl(values[3]*values[3] - 4*0.5*values[5]*(-values[1]))) / values[5];
                    }
                    i++;
                    tChanged = true;
                }
            }


/*-------------------------------------------DERIVED FROM: a=(vf-vi)/t-------------------------------------------------------*/

            if((vfChanged)&&(viChanged)&&(tChanged)){
                if(!aChanged){
                    values[5] = (values[4]-values[3]) / values[0];
                    i++;
                    aChanged = true;
                }
            }
            if((vfChanged)&&(viChanged)&&(aChanged)){
                if(!tChanged){
                    values[0] = (values[4]-values[3]) / values[5];
                    i++;
                    tChanged = true;
                }
            }
            if((vfChanged)&&(aChanged)&&(tChanged)){
                if(!viChanged){
                    values[3] = values[4] - values[5]*values[0];
                    i++;
                    viChanged = true;
                }
            }
            if((viChanged)&&(aChanged)&&(tChanged)){
                if(!vfChanged){
                    values[4] = values[3] + values[5]*values[0];
                    i++;
                    vfChanged = true;
                }
            }


/*-------------------------------------------DERIVED FROM: va = d/t---------------------------------------------------------*/

            if((dChanged)&&(tChanged)){
                if(!vaChanged){
                    values[2] = values[1] / values[0];
                    i++;
                    vaChanged = true;
                }
            }
            if((dChanged)&&(vaChanged)){
                if(!tChanged){
                    values[0] = values[1] / values[2];
                    i++;
                    tChanged = true;
                }
            }
            if((vaChanged)&&(tChanged)){
                if(!dChanged){
                    values[1] = values[2]*values[0];
                    i++;
                    dChanged = true;
                }
            }


/*-------------------------------------------SOLVING FOR t WITH vi--------------------------------------------------------*/

            if((aChanged)&&(vfChanged)&&(dChanged)){
                if(!tChanged){
                    values[0] = (sqrtl(values[4]*values[4] - 2*values[5]*values[1]) + values[4]) / -(values[5]);
                    if(values[0]<=0){
                        values[0] = (sqrtl(values[4]*values[4] -     2*values[5]*values[1]) - values[4]) / -(values[5]);
                    }
                    i++;
                    tChanged = true;
                }
            }


    }while(i>0);
}

This algorithm does work properly but it seems awfully inefficient to me. Can anyone suggest an alternative before I go further into forces and momentum?

\$\endgroup\$
  • \$\begingroup\$ You can split logic across functions instead of calling everything in one function. Also you can move i++ at the end just once instead of every permutation. Nested ifs can be merged together using && condition. \$\endgroup\$ – Rajesh Dec 13 '15 at 4:31
  • \$\begingroup\$ I agree that it should be broken up into multiple functions. I just haven't done that yet because passing all the variables around is a pain in the arse. I'm not sure what you mean by move i++ to the end though. As I see it, it has to be in every if statement because if a single if statement runs then there will be a new variable with which to test all the other if statements again. \$\endgroup\$ – Ulthran Dec 13 '15 at 4:36
  • \$\begingroup\$ My apologies even i realize that but was unable to edit the connect. And about paying variables, you can use global variables. \$\endgroup\$ – Rajesh Dec 13 '15 at 4:38
  • \$\begingroup\$ I do use global variables for the booleans but given that the final program will contain about nine other functions like this one, all using similar variable names and values, I figured it was best to use local variables and pass them around to where they are needed. \$\endgroup\$ – Ulthran Dec 13 '15 at 4:49
4
\$\begingroup\$

What does values[n] mean?

You have this array of 6 doubles, that you reference by index throughout:

values[2] = (values[4]+values[3]) / 2;

What does that line of code mean? Very little. You never use this array like an array - it's not a collection of like things. Instead, you should move these values into a struct:

typedef struct variables {
    double v_a;
    double v_f;
    double v_i;
    ...
};

And then take a struct variables* instead of a double[6] - so you can reference the right thing:

vars->v_a = (vars->v_i + vars->v_f) / 2;

That has meaning. This gets steadily more important as the number of elements in values you use increases.

Refactoring

Rather than one monolith, you should break each formula up into its own piece:

void calculateUnknowns1(struct variables* vars)
{
    int i;
    do {
        i = 0;
        i += applyAvgVelocity(vars);
        i += applyAcceleration(vars);
        ...
    } while (i>0);
}

Or even:

void calculateUnknowns1(struct variables* vars)
{
    while (applyAvgVelocity(vars) || applyAcceleration(vars) || ... )
        ;
}
\$\endgroup\$
3
\$\begingroup\$

Doesn't work if a single variable is changed

It's hard for me to review this because I can't see the rest of the code. But it seems to me that every single && in this function should be a || instead.

For example, if someone only changed vf, your function would do nothing because every clause in your function requires at least two things to change before anything actually happens. That doesn't seem correct.

\$\endgroup\$
  • \$\begingroup\$ Well if it only received vf as an input none of the other variables could be determined. It needs at least two to determine any other. \$\endgroup\$ – Ulthran Dec 13 '15 at 16:46
  • \$\begingroup\$ That's why I said it was hard to review. Your description made it seem like you could modify a variable and have the other ones get updated. The variable name vfChanged made me think that your calculator supported incremental updates. \$\endgroup\$ – JS1 Dec 13 '15 at 19:06
1
\$\begingroup\$

The multiple if statements based on booleans can be optimized in the same way that you optimize digital logic in electronics. Make a logic table like this:

vfChanged viChanged vaChanged  ... (any number of boolean conditions)

Then enumerate all their possible combinations as binary:

vfChanged viChanged vaChanged
        0         0         0    // 0
        0         0         1    // 1
        0         1         0    // 2
        0         1         1    // 3
                          ...
        1         1         1    // 7 = max

Then replace all your bool variables with a single bit field variable, where each bit corresponds to one of the previous variables:

typedef enum
{
  vfChanged = 0x04;
  viChanged = 0x02;
  vaChanged = 0x01;
} change_t;

static change_t change = vfChanged | vaChanged; 

To set the bit flags in change, you'll have to use bitwise operators like shown above, instead of true/false.

Then make a function corresponding to every case and define a common function type. (Since you understand the algorithm, you can probably come up with better function names than me)

Write the functions so that they only contain the actual action to take, and not the boolean checks, which we are optimizing away:

typedef void change_func_t (double [6], int* i);

void change_va (double values[6], int* i)
{
  values[2] = (values[4]+values[3]) / 2;
  (*i)++;
  change |= vaChanged; // bit field variable
}

void change_vi (double values[6], int* i)
{
  ...
}


void change_vf (double values[6], int* i)
{
  ...
}

...

// also add a function which does nothing:
void do_nothing (double values[6], int* i)
{ 
  (void)values;
  (void)i;
}

Now you can define a look-up table of functions corresponding to the logic table above. Do this by declaring an array of function pointers:

const size_t COMBINATIONS = 7; // 7 possible combinations in the logic table

const change_func_t* change_func [COMBINATIONS] =
{
  // nonsense example, set up these in a meaningful way so that the correct action is taken:

  do_nothing, // vfChanged=0 viChanged=0 vaChanged=0
  change_vi,  // vfChanged=0 viChanged=0 vaChanged=1
  change_va,  // vfChanged=0 viChanged=1 vaChanged=0
  change_vf,  // vfChanged=0 viChanged=1 vaChanged=1
  change_vf,  // vfChanged=1 viChanged=0 vaChanged=0
  ...
};

Now you can replace the whole calculateUnknowns1 funtion with this:

void calculateUnknowns1(double values[6])
{
  int i = 0;

  do{
    i = 0;
    change_func[change](values, &i);
  }while(i>0);
}

It seems that the int i variable can be replaced with a boolean(?), which in that case would make the function clearer. You could then have each function returning a bool and make the caller even smaller:

Given typedef bool change_func_t (double [6]);, the caller can be reduced to this:

  while(change_func[change](values))
    ;
\$\endgroup\$
  • \$\begingroup\$ Thanks. Not to be a pain but I'm planning on converting this into an iOS app. Do you know if this is possible in Swift 2? \$\endgroup\$ – Ulthran Dec 14 '15 at 21:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.