-
Notifications
You must be signed in to change notification settings - Fork 44
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
Serializable expressions/operators #1848
Conversation
8c213a1
to
25edfe6
Compare
Looks great. A few random notes / things that came to mind:
Some points tha t would be great to nail down:
For now let's just do invdes. Then we can think about trying to hook this up tom the design plugin pre-process (sim setup). But other ideas include:
I think this is fine for now. Or we can put everything in plugins and move the general stuff into components later, depending on how things go? But definitely the data-specific metric stuff I think should go in plugins for now.
Can we evaluate the expression into a function where the *args correspond to all of the unknown variables? then we just call this function passing our variables?
Maybe we still allow them to write f_callable = lambda a: a + b
f_expression = f_callable(tracer_a) this would maybe have to be done internally, but could be one way of defining the callables and validating them. For example, a user could supply an objective function as a function of |
Hi @yaugenst-flex! When you call |
Good question, the metric part is not fleshed out at all currently, haha. But I think the easiest would be to just supply the monitor name as an argument to a metric? My example from above would become: m1 = ModeCoefficient(mode_index=0, direction="+", monitor_name="monitor1")
m2 = ModeCoefficient(mode_index=0, direction="-", monitor_name="monitor2")
post_process = (abs(m1)**2 + abs(m2)**2) / 2 What do you think? |
@e-g-melo: Yeah that sounds like a cool idea! I guess one caveat is that I'd assume most users do their data postprocessing client-side with all the power of python, so doing it this way is maybe a bit limited. But definitely worthwhile for compatibility and for anyone doing some light postprocessing on the GUI side. @tylerflex: Ok going point by point 😃
Not sure I understand. You mean function in the sense of a regular Python function? Because it is callable like this already right. If that's the case, I'm not sure how we would go about converting a regular function into an Expression, that sounds tricky...
We could do that, although the things that are implemented currently "just work" under autograd or regular numpy, and autodiff works too. What might be easiest to do is if we just design our operators in a way that they just do what you would expect when supplying either scalars or arraylikes to them. I think in most cases this would already work fine if we just made the operators be
Yeah definitely, a lot of this can just be added to the parent class I think, this shouldn't be too hard to do.
I'm fine with either, I'm just thinking if we already decide that we do want to use this for regular components too, then it might make sense to include it directly, since we are going to end up moving the thing anyways.
That makes sense to me, my problem is mostly that that big expression graph has a ton of subgraphs, that's how it is constructed. So the distinction between a variable and an expression is lost as soon as you apply one operator to it. I'll have to think about it some more.
Yes that is possible I think, although we would have to be really careful about the ordering of the
I think this is a cool idea but it will require a non-trivial amount of work I think. It's not only tracing, but also recording the transformations and converting them to our operators. Have to think about it. |
What I mean is defining the postprocesing function in the old style def f(sim_data):
return abs(sim_data['name'].abs.sel(...)**2)
InverseDesign(postprocess=f) and yea I'm not sure how to convert it either, my original thought was like how autodiff compiles callable into computational graph by passing some tracer argument and recording the operations.
This sounds good, but Im more wondering about if the user tries to do (for example using your example) post_process = (np.abs(m1)**2 + np.abs(m2)**2) / 2 would the |
def f(sim_data):
return abs(sim_data['name'].abs.sel(...)**2)
InverseDesign(postprocess=f) Yeah this seems pretty difficult to do, especially if going through a
Yes it would, I think. Well actually in this particular case maybe not, because it might call |
Don't threaten me with a good time :D |
25edfe6
to
c3ca716
Compare
c3ca716
to
256d059
Compare
256d059
to
7e814cd
Compare
Closes #1944 |
1586bc1
to
54903ba
Compare
54903ba
to
4a50587
Compare
Changes from the Original Proposal:
For a more detailed explanation refer to the readme. |
To @momchil-flex 's point, I think it should be possible to just have a very thin wrapper around |
I don't think there is a need for any special handling of autograd, since functions are already implemented as autograd. functions. everything else is differentiable out of the box |
I guess the challenge is how do we allow the user to build these expressions just calling import metrics.numpy as np
np.exp(ModeAmps(...)) and have |
I think the user should just do:
|
I guess what about functions that are not implemented? maybe the idea is to create a class (or programmatically create classes) that implement each of the autograd numpy operations? |
I see. yeah I guess that's possible, but will probably run into a lot of edge cases. in particular, we currently don't really support nary operations, i.e. there is no concept of a list or an array, a
|
hm, so basically if the user doesn't select out all of the data it will error? (eg if two modes are summed over?) |
you sum over two modes by doing
|
3fb6c51
to
ff01229
Compare
3c3ec11
to
ce5f870
Compare
…initial parameters" This reverts commit 74189be.
Closing as merged in #1973 |
EDIT: Details below are outdated, refer to #1848 (comment)
This might not get merged, but serves as a place to discuss the implementation of serializable operators/expressions (and eventually metrics) so that we can do serverside evaluation.
The main idea is that we have
Expression
s andOperator
s that are both instances of aTidy3dBaseModel
. These can be combined into higher-level mathematical expressions using regular Python syntax, and are still serializable. This is essentially a compromise between arbitrary user-defined functions and something that is feasible and secure for us to implement/use.The proposed API looks something like this:
Some points that would be great to nail down:
invdes
anddesign
plugins come to mind, but @tylerflex also mentioned that we might potentially want to add monitors with user-defined postprocessing.Expression
andOperator
incomponents/expressions/
so that it essentially becomes part of tidy3d's core and split theMetrics
part intoplugins/metrics/
, as those might not prove to be generally useful / part of the core machinery. But it might make sense to revisit this.Operation
andMetric
, so I thinkOperator
andExpression
is a step up from that. Any better suggestions?Some rough edges:
post_process
from above) takes exactly one input argument, which is assumed to be aSimulationData
. It might be nice to be able to supply multiple arguments, or even multiple arguments that enter the expression at different points in the evaluation. The former should be easy enough to tack on to the proposed architecture, the latter will require more work.f = a + b
,f
ends up being a callable. Maybe we can come up with some syntactic sugar to make this more obvious.Related to #1828