Note
Go to the end to download the full example code.
Polar Transformation¶
As another example, if we were interested in transforming Cartesian coordinates to polar form:
We can implement this with an ExplicitSystem
by declaring the inputs and outputs
of this system as follows:
import condor as co
from condor.backend import operators as ops
class PolarTransform(co.ExplicitSystem):
x = input()
y = input()
output.r = ops.sqrt(x**2 + y**2)
#output.theta = ops.atan2(y, x)
output.theta = ops.atan(y/x)
In general, once you’ve defined any system in Condor, you can just evaulate it numerically by passing in numbers:
p = PolarTransform(x=3, y=4)
print(p)
<PolarTransform: x=3, y=4>
The output returned by such a call is designed for inspection to the extent that we recommend working in an interactive session or debugger, especially when getting accustomed to Condor features.
For example, the outputs of an explicit system are accessible directly:
print(p.r)
[5.]
They can also be retrieved collectively:
print(p.output)
PolartransformOutput(r=array([5.]), theta=array([0.92729522]))
You can of course call it again with different arguments
print(PolarTransform(x=1, y=0).output.asdict())
{'r': array([1.]), 'theta': array([0.])}
While the binding of the results in a datastructure is nice, the real benefit of
constructing condor models is in calling iterative solvers. For example, we could
perform symbolic manipulation to define another ExplicitSystem
with AlgebraicSystem
by
declaring the input radius and angle as parameter
s and the solving variables for
and letting an iterative solver find the solution
class CartesianTransform(co.AlgebraicSystem):
# r and theta are input parameters
r = parameter()
theta = parameter()
# solver will vary x and y to satisfy the residuals
x = variable(initializer=1)
y = variable(initializer=0)
# get r, theta from solver's x, y
p = PolarTransform(x=x, y=y)
# residuals to converge to 0
residual(r == p.r)
residual(theta == p.theta)
out = CartesianTransform(r=1, theta=ops.pi / 4)
print(out.x, out.y)
[0.70710678] [0.70710678]
Note also that passing the inputs (or any intermediates) to plain numeric functions
that can handle symbolic objects as well as pure numerical objects (float or numpy
arrays) could work for this simple example. However, since we embedded the
PolarTransform
model in this solver, the system evaluated with the solved variable
values is directly accessible if the bind_embedded_models
option is True
(which it is by default), as in:
print(out.p.output)
PolartransformOutput(r=array([1.]), theta=array([0.78539816]))
Note that this has multiple solutions due to the form of the algebraic relationship of
the polar/rectangular transformation. The AlgebraicSystem
uses Newton’s
method as the solver, so the solution that is found depends on the initial conditions.
The initializer
attribute on the variable
field determines the initial
position. For example,
CartesianTransform.set_initial(x=-1, y=-1)
out = CartesianTransform(r=1, theta=ops.pi / 4)
print(out.variable)
CartesiantransformVariable(x=array([-0.70710678]), y=array([-0.70710678]))
An additional warm_start
attribute determines whether the initializer is
over-wrriten. Since the default is true, we can inspect the initializer values,
print(CartesianTransform.x.initializer, CartesianTransform.y.initializer)
[-0.70710678] [-0.70710678]
and re-solve with attr:warm_start False
CartesianTransform.y.warm_start = False
Total running time of the script: (0 minutes 0.025 seconds)