Opened 14 years ago

Closed 6 years ago

#1283 closed defect (fixed)

Assigning matrices is terribly inefficient (unneeded expand)

Reported by: asodja Owned by: Per Östlund
Priority: blocker Milestone: 1.14.0
Component: New Instantiation Version: trunk
Keywords: Cc: asodja

Description (last modified by Martin Sjölund)

The following model calles function EulerAngles with same arguments for every element of the matrix:

model TransformMatrix
  function EulerAngles 
    "Computes transformation matrix for given Euler angles"
    input Real phi;
    input Real theta;
    input Real psi;
    output Real A[4, 4];
  algorithm 
    A := diagonal(vector(ones(1, 4)));
    A[1, 1] := cos(psi)*cos(phi) - cos(theta)*sin(phi)*sin(psi);
    A[1, 2] := cos(psi)*sin(phi) + cos(theta)*cos(phi)*sin(psi);
    A[1, 3] := sin(psi)*sin(theta);
    A[2, 1] := -sin(psi)*cos(phi) - cos(theta)*sin(phi)*cos(psi);
    A[2, 2] := -sin(psi)*sin(phi) + cos(theta)*cos(phi)*cos(psi);
    A[2, 3] := cos(psi)*sin(theta);
    A[3, 1] := sin(theta)*sin(phi);
    A[3, 2] := -sin(theta)*cos(phi);
    A[3, 3] := cos(theta);
  end EulerAngles;
  
  parameter Real phi = 0;
  parameter Real theta = 0;
  parameter Real psi = 0;
  parameter Real T[4, 4]=EulerAngles(phi,theta,psi);  
end TransformMatrix;

Change History (18)

comment:1 by Martin Sjölund, 11 years ago

Cc: asodja, → asodja
Description: modified (diff)

comment:2 by Martin Sjölund, 11 years ago

Description: modified (diff)

comment:3 by Henning Kiel, 10 years ago

Milestone: 1.9.3
Type: defectenhancement
Version: 1.6.0trunk

comment:4 by Martin Sjölund, 9 years ago

Milestone: 1.9.31.9.4

Moved to new milestone 1.9.4

comment:5 by Martin Sjölund, 9 years ago

Milestone: 1.9.41.9.5

Milestone pushed to 1.9.5

comment:6 by Martin Sjölund, 9 years ago

Milestone: 1.9.51.10.0

Milestone renamed

comment:7 by Martin Sjölund, 8 years ago

Milestone: 1.10.01.11.0

Ticket retargeted after milestone closed

comment:8 by Martin Sjölund, 8 years ago

Milestone: 1.11.01.12.0

Milestone moved to 1.12.0 due to 1.11.0 already being released.

comment:9 by Francesco Casella, 7 years ago

Component: Code GenerationNew Instantiation
Milestone: 1.12.02.0.0
Owner: changed from asodja to Per Östlund
Priority: normalblocker
Status: newassigned
Type: enhancementdefect

This ticket is very old but still valid, if I flatten the model with OMEdit 1.12.beta2 I get

class TransformMatrix
  parameter Real phi = 0.0;
  parameter Real theta = 0.0;
  parameter Real psi = 0.0;
  parameter Real T[1,1] = TransformMatrix.EulerAngles(phi, theta, psi)[1, 1];
  parameter Real T[1,2] = TransformMatrix.EulerAngles(phi, theta, psi)[1, 2];
  parameter Real T[1,3] = TransformMatrix.EulerAngles(phi, theta, psi)[1, 3];
  parameter Real T[1,4] = TransformMatrix.EulerAngles(phi, theta, psi)[1, 4];
  parameter Real T[2,1] = TransformMatrix.EulerAngles(phi, theta, psi)[2, 1];
  parameter Real T[2,2] = TransformMatrix.EulerAngles(phi, theta, psi)[2, 2];
  parameter Real T[2,3] = TransformMatrix.EulerAngles(phi, theta, psi)[2, 3];
  parameter Real T[2,4] = TransformMatrix.EulerAngles(phi, theta, psi)[2, 4];
  parameter Real T[3,1] = TransformMatrix.EulerAngles(phi, theta, psi)[3, 1];
  parameter Real T[3,2] = TransformMatrix.EulerAngles(phi, theta, psi)[3, 2];
  parameter Real T[3,3] = TransformMatrix.EulerAngles(phi, theta, psi)[3, 3];
  parameter Real T[3,4] = TransformMatrix.EulerAngles(phi, theta, psi)[3, 4];
  parameter Real T[4,1] = TransformMatrix.EulerAngles(phi, theta, psi)[4, 1];
  parameter Real T[4,2] = TransformMatrix.EulerAngles(phi, theta, psi)[4, 2];
  parameter Real T[4,3] = TransformMatrix.EulerAngles(phi, theta, psi)[4, 3];
  parameter Real T[4,4] = TransformMatrix.EulerAngles(phi, theta, psi)[4, 4];
end TransformMatrix;

and indeed the function is called 16 times when you simulate the model (just add a print statement to the algorithm).

This could be critical when we have large table-based datasets and in general every time matrix functions are not inlined.

Can we make sure that this does not happen with the new front-end?

This issue could be related to #4076 and #4552

comment:10 by Martin Sjölund, 7 years ago

It's pretty similar to PR#1773 (ticket:4496).

comment:11 by Francesco Casella, 7 years ago

Does this PR avoid calling the function NxM times?

comment:12 by Martin Sjölund, 7 years ago

It only deals with (scalar?) records, but I guess it's pretty much the same thing and just needs to be generalized. For records it is more important though because if we didn't move them, the equations were simply dropped.

comment:13 by Francesco Casella, 6 years ago

Still valid as of v1.13.0-dev-798-g1c8bb86de

in reply to:  13 comment:14 by Per Östlund, 6 years ago

Replying to casella:

Still valid as of v1.13.0-dev-798-g1c8bb86de

Any suggestion on how this could be handled? The alternatives I could think of are:

  1. Evaluate the function. The function arguments would become structural, and it wouldn't work with non-parameter arguments.
  2. Inline the function. That would work in this particular case, but not in general.
  3. Make the binding an initial equation and make sure the parameter isn't fixed, like we do with some complex bindings. I'm not sure what issues that could cause.
  4. Pass the parameter to the backend without scalarizing it. That will probably require a lot of work in the backend.

comment:15 by Francesco Casella, 6 years ago

Unfortunately the original ticket lacks a bit of context. Anton Sodja used to work on a thermal model of a small building in Slovenia for his PhD thesis, and if I am not mistaken, what I recognize here is a function to compute the direction of the Sun's rays given its Euler angles. I don't know why this is a parameter in this case and not a time-varying function.

I agree that inlining would be good, but I understand we are currently only inlining functions whose algorithm is a one-liner. I did some work in the past to extend this to more complicated algorithms, but it is definitely non-trivial.

Passing the parameter to the backend without scalarizing it is not a viable solution for 2.0.0 (nor for 2.1.0, I guess). If we make the binding an initial equation, we are again passing the buck to the back-end, which should figure out the internal structure of the function.

I think the only viable solution here is to evaluate the function in the front-end. As you mentioned, this makes the computation much more efficient, but it doesn't allow to change the parameter at runtime, and vice-versa if you don't evaluate.

I guess we should leave this choice to the model developer, by means of annotation(Evaluate = true); annotations. In general, the model developer is the best candidate to take informed decisions about this kind of trade-offs, that a tool cannot do because it lacks context information and understanding.

As a model developer, the question is: where do I need to put those annotations? A naive answer would be on parameter T, but as it depends on other parameters, the actual semantics is a bit unclear: does the compiler also evaluate them, or it doesn't even evaluate T, since it cannot evaluate the other parameters?.

If I set annotation(Evaluate = true); on phi, theta, and psi, and set T to be final, the compiler should figure out that it can evaluate T at compile time. Unfortunately this doesn't work (yet). It doesn't work also if I set annotation(Evaluate = true); on all the parameters of this model.

My proposal is that the following model should evaluate the function only once in the front end

model TransformMatrix
  function EulerAngles 
    "Computes transformation matrix for given Euler angles"
    input Real phi;
    input Real theta;
    input Real psi;
    output Real A[4, 4];
  algorithm 
    A := diagonal(vector(ones(1, 4)));
    A[1, 1] := cos(psi)*cos(phi) - cos(theta)*sin(phi)*sin(psi);
    A[1, 2] := cos(psi)*sin(phi) + cos(theta)*cos(phi)*sin(psi);
    A[1, 3] := sin(psi)*sin(theta);
    A[2, 1] := -sin(psi)*cos(phi) - cos(theta)*sin(phi)*cos(psi);
    A[2, 2] := -sin(psi)*sin(phi) + cos(theta)*cos(phi)*cos(psi);
    A[2, 3] := cos(psi)*sin(theta);
    A[3, 1] := sin(theta)*sin(phi);
    A[3, 2] := -sin(theta)*cos(phi);
    A[3, 3] := cos(theta);
  end EulerAngles;
  
  parameter Real phi = 0 annotation(Evaluate = true);
  parameter Real theta = 0 annotation(Evaluate = true);
  parameter Real psi = 0 annotation(Evaluate = true);
  final parameter Real T[4, 4]=EulerAngles(phi,theta,psi);  
end TransformMatrix;

is this doable (possibly by adding the annotation also on the declaration of T)?

in reply to:  15 ; comment:16 by Per Östlund, 6 years ago

Replying to casella:

is this doable (possibly by adding the annotation also on the declaration of T)?

Certainly, setting Evaluate = true should make the parameters structural (the NF doesn't do so yet though), and thus treated as constants by the frontend. Although the NF doesn't evaluate functions unless it has to, even if all arguments are constant. But in cases such as this it probably should.

in reply to:  16 comment:17 by Francesco Casella, 6 years ago

Replying to perost:

Replying to casella:

is this doable (possibly by adding the annotation also on the declaration of T)?

Certainly, setting Evaluate = true should make the parameters structural (the NF doesn't do so yet though)

I opened #5023 on this specific issue, I was not aware of this.

and thus treated as constants by the frontend. Although the NF doesn't evaluate functions unless it has to, even if all arguments are constant.

Why not? Is it to avoid potential problems if it can't evaluate them (e.g. because of external functions, see #2565)?

But in cases such as this it probably should.

Absolutely. If the model developer sets Evaluate=true the FE should try hard to do that.

In principle, the specification says that the modeller "proposes" to do that, so it's not strictly compulsory, but in practice in all cases I am aware of, the modeller is begging the compiler to exploit the structural consequences of those parameters fully, and the consequences or not doing so range from very bad to disastrous.

comment:18 by Francesco Casella, 6 years ago

Milestone: 2.0.01.14.0
Resolution: fixed
Status: assignedclosed

Using the NF, the following model

model TransformMatrix
  function EulerAngles 
    "Computes transformation matrix for given Euler angles"
    input Real phi;
    input Real theta;
    input Real psi;
    output Real A[4, 4];
  algorithm 
    A := diagonal(vector(ones(1, 4)));
    A[1, 1] := cos(psi)*cos(phi) - cos(theta)*sin(phi)*sin(psi);
    A[1, 2] := cos(psi)*sin(phi) + cos(theta)*cos(phi)*sin(psi);
    A[1, 3] := sin(psi)*sin(theta);
    A[2, 1] := -sin(psi)*cos(phi) - cos(theta)*sin(phi)*cos(psi);
    A[2, 2] := -sin(psi)*sin(phi) + cos(theta)*cos(phi)*cos(psi);
    A[2, 3] := cos(psi)*sin(theta);
    A[3, 1] := sin(theta)*sin(phi);
    A[3, 2] := -sin(theta)*cos(phi);
    A[3, 3] := cos(theta);
    Modelica.Utilities.Streams.print("function called");
  end EulerAngles;
  
  parameter Real phi = 0 annotation(Evaluate = true);
  parameter Real theta = 0 annotation(Evaluate = true);
  parameter Real psi = 0 annotation(Evaluate = true);
  parameter Real T[4, 4]=EulerAngles(phi,theta,psi) annotation(Evaluate = true); 
end TransformMatrix;

with the Evaluate annotation on all parameters triggers the evaluation of T in the front-end, so the function is not called 16 times at runtime.

Note: See TracTickets for help on using tickets.