Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Reconstruction constructor in CoordSys3D #13056

Merged
merged 29 commits into from
Aug 12, 2017

Conversation

szymag
Copy link
Contributor

@szymag szymag commented Jul 27, 2017

This PR introduces to constructor transformation equation.
These equations will be obtain from rotation_matrix, location (for translations), and for changing type of coordinate system. After obtaining these equations, method for transformation composition will be used.

We need to deal with two situation. When we want to connect our coordinate system (parent coordinate system) and when we want to just set transformation equations.

trans_eq3 = matrix[6]*self.x + matrix[7]*self.y + matrix[8]*self.z
trans_eq1 = matrix[0]*variables[0] + matrix[1]*variables[1] + matrix[2]*variables[2]
trans_eq2 = matrix[3]*variables[0] + matrix[4]*variables[1] + matrix[5]*variables[2]
trans_eq3 = matrix[6]*variables[0] + matrix[7]*variables[1] + matrix[8]*variables[2]

return trans_eq1, trans_eq2, trans_eq3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we operate with lambda, it would be easier.

@@ -98,6 +98,33 @@ def __new__(cls, name, location=None, rotation_matrix=None,
location = Vector.zero
origin = Point(name + '.origin')

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What to do here:

  • check that if ŧransformation is None.
  • if it is None, computer transformation from the rotation and traslation. Make identity if they are also None.
  • if it is not None, check that rotation and translation are None
  • transform transformation to a Lambda.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why do that.
You assume that we have to use transformation or (rotation and translation), so transformation should have information about every three transformations.
I think that it could be easier sometimes to split transformation into steps. For example for transformation set cylindrical and for location set some vector.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do it the general way, no need to complicate the code with special cases. Translations are subsets of transformations. In the special cases of rotation and translations, we could store some markers to remind of the special case (and remember the steps).

@Upabjojr
Copy link
Contributor

I suggest to return lambda objects from the methods, so you can compose the transformations by simply calculating f(*g(x)).

@szymag
Copy link
Contributor Author

szymag commented Jul 29, 2017

I suggest to return lambda objects from the methods, so you can compose the transformations by simply calculating f(*g(x)).

I used your idea in constructor, when rotation_equations and translation_equations are obtained.

@@ -109,12 +156,11 @@ def __new__(cls, name, location=None, rotation_matrix=None,
# they may actually be 'coincident' wrt the root system.
if parent is not None:
obj = super(CoordSys3D, cls).__new__(
cls, Symbol(name), location, parent_orient, parent)
cls, Symbol(name), transformation, parent)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transformation need to be defined.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transformation has to be a SymPy type, if we are going to use a lambda, we need to use SymPy's Lambda (with capital L).

if parent is None:
transformation_equations = None
else:
transformation_equations = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The aim is to define transformation, we may not need transformation_equations.

raise ValueError

if parent is None:
transformation_equations = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why None? We have transformation != None in this case. We should just check that transformation is correct (and transform it to a SymPy type).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't implement this part yet.
I just put None to create some structure for case when transformation is not None.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we need to think first, how transformation should look like. Should we just use _connect_to_standard_cartesian?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That code can be OK. I would prefer that transformation be finally transformed into a Lambda object, it makes more sense with the hashing.

@@ -174,7 +220,7 @@ def __new__(cls, name, location=None, rotation_matrix=None,

obj._parent_rotation_matrix = parent_orient
obj._origin = origin

obj.transformation_equations = transformation_equations
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Store transformation, not transformation_equations (they are the same thing in the end).

raise ValueError("The transformation equation does not "
"create orthogonal coordinate system")
else:
if not (location and rotation_matrix) is None:
Copy link
Contributor

@Upabjojr Upabjojr Jul 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition is wrong. Both have to be None otherwise raise an exception

lame_coefficients = connect_to[1]
inverse_transformation = connect_to[2]
else:
transformation = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't cover this scenario. When you accept the case when parent is None I will update this part.
I wrote about it on gitter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the parent case. Sorry, didn't read gitter.

# See lines 80:99. Location is somehow treated only when parent is put.
# We are not able to create independent vector, so we can't assign
# anything to location.
translation_equations = lambda x, y, z: (x, y, z)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we should treat the parent as being a default standard coordinate system if it is not specified. Otherwise what sense does it make to specify the transformation?

x, y, z = symbols('x y z')
transformation = rotation_equations(*translation_equations(x, y, z))
lame_coefficients = _set_lame_coefficient_mapping('cartesian', (x, y, z))
inverse_transformation = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this could be specified. You just have to immagine that there is some default coordinate system used as a reference if parent == None.

lame_coefficients = _set_lame_coefficient_mapping('cartesian', (x, y, z))
inverse_transformation = None

if not CoordSys3D._check_orthogonality(transformation, (x, y, z)):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there are only rotations and translations, we don't need this check.

@@ -98,6 +213,65 @@ def __new__(cls, name, location=None, rotation_matrix=None,
location = Vector.zero
origin = Point(name + '.origin')

if transformation is None:
if parent is None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if loop is not very useful. The case where parent == None and parent != None could be handled by the same code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be, but the one difference between these two cases is with variables. When we have parent we can construct transformation equations using base scalars from parent system. Otherwise we can't.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using lambdas in order to avoid this problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I see. I will simplify this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing is translation. Without parent it doesn't have sense, so we need to distinguish two cases. But I think we can still simplify the code a bit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't it make sense? Anyway, the code in the master supports location even without parent, so we need to keep the compatibility.

lame_coefficients = _set_lame_coefficient_mapping('cartesian', parent.base_scalars())
inverse_transformation = _set_inv_trans_equations('cartesian', parent.base_scalars())

if not CoordSys3D._check_orthogonality(transformation, parent.base_scalars()):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useless check, it will always pass (for rotations and translations).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

if location is not None and rotation_matrix is not None:
raise ValueError

if parent is None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove this if.

@szymag szymag force-pushed the reconstruction_constructor branch from 5ef983a to 181c2a5 Compare August 1, 2017 20:18
else:
inverse_transformation_equations = None

elif isinstance(curv_coord_type, (tuple, list, Tuple, collections.Callable)):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callable should be a separate case.

Upabjojr and others added 3 commits August 1, 2017 21:24
Almost finished constructor.

Lame_coefficinets, transformation, inverse_transfromation are Lambda,
so they can easily depend on desired variables.

Creating base scalars with custom str has the same form as base scalars
created in standard name: class_name + '.' + variable_names.
rotation_lambda = lambda x, y, z: (x, y, z)
if rotation_matrix is not None:
rotation_lambda = \
lambda x, y, z: parent._rotation_trans_equations(parent_orient, (x, y, z))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with lambda no need for parent here.

obj._inverse_transformation = inverse_transformation
obj._h1, obj._h2, obj._h3 = lame_coefficients(obj._x, obj._y, obj._z)
if parent is not None:
obj._transformation = transformation(parent._x, parent._y, parent._z)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think transformation should remain a Lambda.

obj._transformation = transformation(parent._x, parent._y, parent._z)
else:
obj._transformation = transformation(obj._x, obj._y, obj._z)
obj._inverse_transformation = inverse_transformation(obj._x, obj._y, obj._z)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also this one, I think it's easier if it remains in the lambda form.


"""

x1, x2, x3 = symbols("x1, x2, x3", cls=Dummy)

if isinstance(curv_coord_type, string_types):
if variables is None:
variables = _default_variables(curv_coord_type)

transformation_equations = _set_transformation_equations_mapping(curv_coord_type)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transformation_equations is a lambda, we need a SymPy Lambda (capital L) for hashing and internal stuff of SymPy.

raise ValueError("The transformation equation does not "
"create orthogonal coordinate system")

lame_coefficients = cls._calculate_lame_coefficients(transformation_equations(*variables), variables)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need to pass variables here? I believe a single lambda is enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was the most difficult part for me and I didn't find clear solution with lambda but I can try once again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in _calculate_lame_coefficients you can use some Dummy variables.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I will try.

Remove parameter variables from _calculate_lame_coefficinets,
_calculate_inv_trans_equations, _check_orthogonality

All transformation in constructor are Lambda object.
Inverse transformation is calculated only when user needs it.
when transformation is choosen from pre-defined ones, inverse is
also set.
@szymag szymag force-pushed the reconstruction_constructor branch from ed4caf2 to ad3b703 Compare August 4, 2017 15:51
szymag and others added 2 commits August 5, 2017 22:00
Remove dependency of parameter variables from every method.
Change form of setting transformation. Variable names should be put
together with equations.

Remove simplify in _calculate_lame_coefficients for a while.
Useage of simplify produce errors which isn't resolve yet.
if transformation is None:
transformation = Tuple(rotation_matrix, location)

if isinstance(transformation, Tuple):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why that?
In this case, we are not able to set transformation as equations with variables. Previously, we could do that. For example:

a = CoordSys3D('a', transformation=Tuple(((x*cos(y), x*sin(y), z)), (x, y, z))) 

But now you assumed that in following manner we're providing rotation and translation transformations.
Look at the code in _connect_to_cartesian method, lines 363:367

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought a bit over the weekend, if we pass a rotation and/or translation, it needs to be stored in transformation and passed to the superclass (SymPy's hashing requirements). If we transform the rotation and/or translation into a Lambda, the code will slow down a lot, because when the class is reconstructed all orthogonality checks and computations are performed (at least, I think that's the problem).

We can still pass the transformation equations by wrapping them in a Lambda.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main problem is that when calling the superclass in the constructor, we need a fixed argument order. The same argument order must return the same object if called on the constructor. This requirement is pretty restrictive on the way we may write the constructor, so we need to choose a precise way to pack the information without losing speed.

szymag and others added 3 commits August 9, 2017 20:11
raises(ValueError, lambda: a._connect_to_standard_cartesian((x, y, z)))


def test_check_orthogonality():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why has this test disappeared?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must did it earlier, but I shouldn't. I will do it

assert simplify(a.lame_coefficients()) == (1, sqrt(a.x**2), 1)

raises(ValueError, lambda: a._connect_to_standard_cartesian((x, y, z)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can test invalid parameters to the constructor

v.append(a*N.i - b*N.j)
v.append((a**2 + N.x)*N.i + N.k)
v.append((a**2 + b)*N.i + 3*(C.y - c)*N.k)
v.append(a * N.i)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes should be reverted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why, but some expression change their order like was c - C.y and know C.y - c

⎛ 2 ⎞ N_i + ⎛⌠ ⎞ N_k\n\
⎝a + b⎠ ⎜⎮ f(b) db⎟ \n\
⎝⌡ ⎠ \
""")
pretty_v_11 = u(
"""\
"""\
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice that. I will revert

@@ -68,26 +70,26 @@ def upretty(expr):

for x in v:
d.append(x | N.k)
s = 3*N.x**2*C.y
s = 3 * N.x ** 2 * C.y
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you use some auto-formatter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, by mistake

assert a._check_orthogonality() is True
x, y, z = symbols('x y z')
u,v = symbols('u, v')
a = CoordSys3D('a', transformation=((x*sin(y)*cos(z), x*sin(y)*sin(z), x*cos(y)), (x, y, z)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't the tuple (x, y, z) come before the expression?

That's why I suggest to simply use: lambda x, y, z: (x*sin(y)*cos(z), x*sin(y)*sin(z), x*cos(y))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add also support for lambda.

assert simplify(a.lame_coefficients()) == (1, sqrt(a.x**2), 1)
a._connect_to_standard_cartesian(((x, y, z), (x * sin(y) * cos(z), x * sin(y) * sin(z), x * cos(y))), inverse=False)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the block from here on still missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not support now these type of transformation.
Here we mainly tested _connect_to_standard_cartesian which now is removed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pass a lambda to CoordSys3D constructor. You added support for this tuples.

raise TypeError("rotation_matrix should be an Immutable" +
"Matrix instance")
parent_orient = rotation_matrix
rotation_matrix = rotation_matrix.as_immutable()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be de-indented

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

Variables now are first in tuple.

Add support for python's lambda.

Add test for wrong transformation argument in constructor.

Fix indent inconstructor.
@Upabjojr Upabjojr merged commit c08f558 into sympy:master Aug 12, 2017
@szymag szymag deleted the reconstruction_constructor branch August 23, 2017 08:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants