Trazar usando Con versus Parcela usando Bloque (Mathematica)

Quiero describir un problema que he tenido con Plot usando With para mantener los parámetros definidos ‘local’. No estoy necesariamente pidiendo una solución: el problema que tengo es de comprensión.

A veces utilizo una construcción como la siguiente para obtener una Parcela:

Método 1

 plot1 = With[{vmax = 10, km = 10}, Plot[Evaluate@((vmax x)/(km + x)), {x, 0, 100}, AxesOrigin -> {0, 0}]] 

Me gusta este método, y es razonablemente claro, incluso para los usuarios que no son de Mathematica , exactamente lo que está sucediendo.

Cuando las ecuaciones a trazar se vuelven más complejas, me gusta definirlas como externas a la ttwig (usando SetDelayed). Por ejemplo:

 f[x_] := (vmax x)/(km + x) 

Sin embargo, lo siguiente no funciona

Método 2

 plot2 = With[{vmax = 10, km = 10}, Plot[Evaluate@f[x], {x, 0, 100}, AxesOrigin -> {0, 0}]] 

Siempre he ingenuamente pensado que debería. Sin embargo, según la statement de Ayuda que

Trazar trata la variable x como local, utilizando efectivamente Block

He usado varias soluciones, principalmente algo como lo siguiente

Método 3

 plot3 = Plot[With[{vmax = 10, km = 10}, Evaluate@f[x]], {x, 0, 100}, AxesOrigin -> {0, 0}] 

Éste parece muy incómodo, y por lo general requiere una explicación adicional incluso para los usuarios de Mathematica .

Trazado de resultados

enter image description here

Sin embargo, recientemente descubrí por casualidad que la sustitución de Block por With en el Método 2 funciona exactamente como se esperaba.

Puedo, por ejemplo, hacer algo como lo siguiente (que a mí me parece un enfoque muy versátil):

 plot4 = Block[{vmax = {10, 10, 10}, km = { 10, 100, 1000}}, Plot[Evaluate@f[x], {x, 0, 100}, AxesOrigin -> {0, 0}, PlotStyle -> {Red, Green, Blue}]] 

dando

enter image description here

Mis preguntas son las siguientes. ¿Cuál es la explicación de la diferencia de comportamiento con With en el Método 1 y 2? ¿Debería haber esperado que el Método 2 no funcionara? Además, ¿cuál es la explicación de la diferencia de comportamiento con Block and With en el Método 2? ¿Debería haber sido capaz de predecir que Block funcionaría?

Curiosamente, muchos más experimentados me sugirieron soluciones alternativas, pero nadie sugirió usar Block .

Finalmente, necesito mantener vmax y km local. (Se han definido algebraicamente en otro lugar)

Su pregunta no es tanto sobre Plot como sobre cómo funcionan las construcciones de scope. La principal confusión aquí se debe a las diferencias entre el scope léxico y dynamic. Y el principal culpable es esta definición:

 f[x_] := (vmax x)/(km + x) 

El problema es que hace que f dependa implícitamente de los símbolos globales (variables) vmax y km . Estoy muy en contra de este tipo de construcciones, ya que conducen a una confusión infinita. Ahora, lo que sucede se puede ilustrar con el siguiente ejemplo:

 In[55]:= With[{vmax =1, km = 2},f[x]] Out[55]= (vmax x)/(km+x) 

Para entender por qué sucede esto, uno debe entender lo que significa el scope léxico . Sabemos que With tiene un atributo HoldAll . La forma en que funciona es que se ve lo que está literalmente dentro de ella y sustituye las variables que se encuentran literalmente en el cuerpo con sus valores de la lista de declaraciones. Esto sucede durante la etapa de unión variable, y solo entonces permite que el cuerpo lo evalúe. A partir de esto, está claro que lo siguiente funcionará:

 In[56]:= With[{vmax =1, km = 2},Evaluate[f[x]]] Out[56]= x/(2+x) 

Esto funcionó porque Evaluate anula la “parte” del atributo HoldAll de With , lo que obliga al cuerpo a evaluar antes que a nada (vinculación variable y posterior evaluación del cuerpo). Por lo tanto, sería completamente equivalente a usar simplemente With[{vmax = 1, km = 2}, (vmax x)/(km + x)] arriba, como se puede ver con Trace . La siguiente parte del rompecabezas es por qué

 With[{vmax = 10, km = 10}, Plot[Evaluate@f[x], {x, 0, 100}, AxesOrigin -> {0, 0}]] 

No funciona. Esto se debe a que esta vez no evaluamos el cuerpo primero. La presencia de Evaluate afecta solo f[x] dentro de Plot , pero no la evaluación de Plot dentro de With . Esto está ilustrado por

 In[59]:= With[{vmax = 10, km = 10}, q[Evaluate@f[x]]] Out[59]= q[(vmax x)/(km + x)] 

Además, no queremos que Plot evalúe primero, ya que los valores de vmax y km no se definirán. Sin embargo, todo lo que With ve es f[x] , y como los parámetros vmax y km no están literalmente presentes allí (scope léxico, recuerde), no se realizará ninguna sustitución. ¿Deberíamos usar Block aquí, y las cosas funcionarán, porque Block usa el scope dynamic, lo que significa que redefine los valores en el tiempo (parte de la stack de ejecución si lo desea), en lugar de en su lugar. Por lo tanto, usando el Block[{a =1, b =2}, ff[x]] donde ff implícitamente depende de a y b es (aproximadamente) equivalente a a=1;b=2;ff[x] (con la diferencia que a y b reanudan sus valores globales después de dejar el scope del Block ). Asi que,

 In[60]:= Block[{vmax = 10, km = 10}, q[Evaluate@f[x]]] Out[60]= q[(10 x)/(10 + x)] 

Para hacer que la versión With funcione, deberías inyectar la expresión para f[x] (rhs), por ejemplo, así:

 In[63]:= Unevaluated[With[{vmax = 10, km = 10}, q[f[x]]]] /. DownValues[f] Out[63]= q[(10 x)/(10 + x)] 

Tenga en cuenta que esto no funcionará:

 In[62]:= With[{fx = f[x]}, With[{vmax = 10, km = 10}, q[fx]]] Out[62]= q[(vmax x)/(km + x)] 

Pero la razón aquí es bastante sutil: mientras que externa With evalúa antes que la interna, detecta los conflictos de nombre de variable y cambia el nombre de sus variables. Las reglas son mucho más disruptivas, no respetan los constructos internos de scope.

EDITAR

Si uno insiste en anidar With -s, aquí es cómo se puede engañar al mecanismo de resolución de conflictos de With y hacer que funcione:

 In[69]:= With[{fx = f[x]}, With @@ Hold[{vmax = 10, km = 10}, q[fx]]] Out[69]= q[(10 x)/(10 + x)] 

Dado que el exterior With ya no puede detectar la presencia de With interno (usando Apply[With,Hold[...]] hace que el interno With genere dinámicamente de forma efectiva), no hace ningún cambio de nombre, y luego funciona. Este es un truco general para engañar al mecanismo de resolución de nombres del scope léxico cuando no se desea cambiar el nombre, aunque la necesidad de usarlo normalmente indica un mal diseño.

FIN EDITAR

Pero me desvié. Para resumir, hacer que su segundo método funcione es bastante difícil y requiere construcciones realmente extrañas como

 Unevaluated[ With[{vmax = 10, km = 10}, Plot[Evaluate@f[x], {x, 0, 100}, AxesOrigin -> {0, 0}]]] /. DownValues[f] 

o

 With[{fx = f[x]}, With @@ Hold[{vmax = 10, km = 10}, Plot[Evaluate@fx, {x, 0, 100}, AxesOrigin -> {0, 0}]]] 

Una vez más: todo esto se debe a que With debe “ver” las variables explícitamente en el código, para hacer reemplazos. Por el contrario, Block no necesita eso, reemplaza los valores dinámicamente en el momento de la evaluación, en función de sus valores globales modificados, como si hiciera asignaciones, por eso funciona.

Ahora, el verdadero culpable es tu definición de f . Podrías haber evitado todos estos problemas si hubieras definido tu f con un paso de parámetros explícito:

 ff[x_, vmax_, km_] := (vmax x)/(km + x) 

Ahora, esto funciona de la caja:

 With[{vmax = 10, km = 10}, Plot[Evaluate@ff[x, vmax, km], {x, 0, 100}, AxesOrigin -> {0, 0}]] 

porque los parámetros están explícitamente presentes en la firma de la llamada a la función, y por lo tanto son visibles para With .

Para resumir: lo que observaste es una consecuencia de la interacción entre el scope léxico y dynamic. Las construcciones de scope léxico deben “ver” sus variables explícitamente en el código en la etapa de vinculación variable (antes de la evaluación), o no serán efectivas. El scope dynamic modifica de forma efectiva los valores de los símbolos y, en este sentido, es menos exigente (el precio que paga es que el código que utiliza muchos ámbitos dynamics es más difícil de entender, ya que mezcla el estado y el comportamiento). La razón principal del problema es la definición de la función que realiza dependencias implícitas en los símbolos globales (que no están en la lista de parámetros formales de la función). Lo mejor es evitar tales construcciones. Todavía es posible hacer que las cosas funcionen, pero esto es considerablemente más complicado (como se demostró anteriormente) y, al menos para el caso que nos ocupa, sin una buena razón.

Solo dos comentarios:

  • usando Block, no es necesario usar Evaluate. Es decir, el Bloque [{vmax = 10, km = 2}, Gráfico [f [x], {x, 0, 100}] funcionará.

  • Otra forma de hacerlo es definir reglas de sustitución: rule = {vmax -> 10, km -> 10}; Parcela [f [x] /. rule, {x, 0, 100}] La ventaja es que puede reutilizar la regla en otras declaraciones.