Modelar una partícula:
Yo estaba esperando
x = 1
v = 2
m = 0.5
0.5
dt = 0.125
x += v*dt
1.25
x2 = 2
v2 = 10
10
N = 10 # numero de particulas
10
x = zeros(N)
v = ones(N);
Muchos hicieron
particula = [1, 2, 0.5]
3-element Array{Float64,1}: 1.0 2.0 0.5
La posición es
particula[1]
1.0
Para actualizar su posición:
particula[1] += dt*particula[2]
# equivalente a particula[1] = particula[1] + dt*particula[2]
1.5
¡Es illegible! Queremos nombres que reflejen la función de cada variable: x
y v
, o pos
y vel
, o posicion
y velocidad
Es la solución: combinación de variables con nombres, adentro de una sola estructura.
type Particula
x
v
m
end
Aquí estamos definiendo un nuevo tipo de datos, que se llama Particula
, y especificamos las características / propiedades internas de lo que es una Particula
-- la estructura de una Particula
.
Define una plantilla de una cajita que contiene espacio para estas tres variables (x
, v
y m
).
¡Todavía no hay ninguna partícula! Para crear un objeto de este tipo (Particula
): Utilizo Particula
como si fuera una función:
p = Particula(1., 2., 0.5)
Particula(1.0,2.0,0.5)
p
Particula(1.0,2.0,0.5)
p.x
1.0
p.v
2.0
p.m
0.5
typeof(p)
Particula (constructor with 1 method)
Quiero definir una función mover
que acepte un objeto de tipo Particula
:
function mover(p, dt)
p.x + p.v*dt
end
mover (generic function with 1 method)
p
Particula(1.0,2.0,0.5)
mover(p, 0.1)
1.2
p
Particula(1.0,2.0,0.5)
¡No se movió! Sólo se calculó la posición nueva. Para mover realmente el objeto, tenemos que cambiar sus propiedades internas:
function mover!(p, dt)
# ! es una convención en Julia que dice que la función
# *modifica* su argumento -- tiene un *efecto secundario* (side effect)
p.x += p.v*dt
end
mover! (generic function with 1 method)
mover!(p, 1)
3.0
p
Particula(3.0,2.0,0.5)
No llamé a la función mover_particula
mover!(1, 0.1)
type Int64 has no field x while loading In[27], in expression starting on line 1 in mover! at In[24]:4
workspace() # borra todo lo que está definido
type Prueba
x
v
David
end
p = Particula(1,2,3) # borré la definición
Particula not defined while loading In[32], in expression starting on line 1
function mover!(p, dt)
# ! es una convención en Julia que dice que la función
# *modifica* su argumento -- tiene un *efecto secundario* (side effect)
p.x += p.v*dt
end
mover! (generic function with 1 method)
d = Prueba(1, 2, 3)
Prueba(1,2,3)
mover!(d, 0.1)
1.2
d
Prueba(1.2,2,3)
Mi función como tal mueve cualquier cosa que tenga un x
y v
adentro. Si no quiero esto:
type Particula
x::Float64 # sólo puede ser de tipo Float64
v
m
end
function mover!(p::Particula, dt)
# sólo actúa sobre objetos de tipo Particula
p.x += p.v*dt
end
mover! (generic function with 2 methods)
p = Particula(1, 2, 0.5)
Particula(1.0,2,0.5)
mover!(p, 0.1)
1.2
p
Particula(1.2,2,0.5)
methods(mover!)
mover!(p, dt) = println("¡HOLA!")
mover! (generic function with 2 methods)
p
Particula(1.2,2,0.5)
mover!(p, 0.1)
1.4
p
Particula(1.4,2,0.5)
mover!(d, 0.1)
¡HOLA!
mover!(i::Int) = println(i)
mover! (generic function with 3 methods)
methods(mover!)
mover!(1)
1
Un gas consiste en muchas partículas.
workspace()
type Particula
x
v
m
end
type Gas_Malo
x::Vector{Float64}
v::Vector{Float64}
m::Vector{Float64}
end
Esto es una mala idea, ya que no vemos que un Gas
consiste en un montón de Partículas
.
type Gas
N::Int
particulas::Vector{Particula}
end
Julia automáticamente (por defecto) crea una función constructora (o un constructor): una función con el mismo nombre que el tipo, que toma los datos y crea un objeto nuevo de este tipo, con los datos adentro.
g = Gas(2, [Particula(1,2,3), Particula(4, 5, 6)] )
Gas(2,[Particula(1,2,3),Particula(4,5,6)])
g.N
2
g.particulas
2-element Array{Particula,1}: Particula(1,2,3) Particula(4,5,6)
g2 = Gas(5, [Particula(1,2,3), Particula(4, 5, 6)] )
Gas(5,[Particula(1,2,3),Particula(4,5,6)])
Aquí estamos violando un invariante: queremos que el número N
siempre sea igual al número de partículas en la lista: yo creo un nuevo constructor (que utiliza el constructor que ya había):
function Gas(particulas::Vector{Particula})
Gas(length(particulas), particulas)
end
Gas (constructor with 3 methods)
methods(Gas)
g2 = Gas([Particula(1,2,3), Particula(4, 5, 6)])
Gas(2,[Particula(1,2,3),Particula(4,5,6)])
Si quiero hacer una lista vacía de partículas e irla ampliando:
particulas = Particula[]
0-element Array{Particula,1}
l = []
0-element Array{None,1}
NB: En Julia 0.4, l=[]
hace un arreglo de Any
, ya no de None
.
[3,4]
2-element Array{Int64,1}: 3 4
v = Int[3,4]
2-element Array{Int64,1}: 3 4
push!(v, 7)
3-element Array{Int64,1}: 3 4 7
push!(v, "David")
`convert` has no method matching convert(::Type{Int64}, ::ASCIIString) while loading In[85], in expression starting on line 1 in push! at array.jl:460
Float64[3,4]
2-element Array{Float64,1}: 3.0 4.0
String[3,4]
`convert` has no method matching convert(::Type{String}, ::Int64) while loading In[79], in expression starting on line 1 in getindex at array.jl:121
w = Any[3,4]
2-element Array{Any,1}: 3 4
push!(w, "David")
3-element Array{Any,1}: 3 4 "David"
particulas = Particula[]
0-element Array{Particula,1}
push!(particulas, Particula(1, 2, 3))
1-element Array{Particula,1}: Particula(1,2,3)
methods(mover!)
mover! not defined while loading In[88], in expression starting on line 1
function mover!(g::Gas)
# algo
end
mover! (generic function with 1 method)
Haz un gas ideal:
x
y v
que son vectores con 2 entradas)Gas
(cuidar que el número de partículas se actualice)Objetos que representan pares $(u(x_0), u'(x_0))$ para una función $u: \mathbb{R} \to \mathbb{R}$
workspace()
type ValorDeriv
val
der
end
a = ValorDeriv(1, 2)
b = ValorDeriv(3, 4)
ValorDeriv(3,4)
a + b
`+` has no method matching +(::ValorDeriv, ::ValorDeriv) while loading In[119], in expression starting on line 1
+(a::ValorDeriv, b::ValorDeriv) = ValorDeriv(a.val+b.val, a.der+b.der)
+ (generic function with 120 methods)
a + b
ValorDeriv(4,6)
a
y b
representan funciones $a(x)$ y $b(x)$ en algún lugar $x_0$ (implícito -- no viene representado explícitamente): a
representa
$\bar{a} = (a(x_0), a'(x_0))$
a + b
representa $((a+b)(x_0), (a+b)'(x_0))$
*(z::Number, a::ValorDeriv) = ValorDeriv(z*a.val, z*a.der)
* (generic function with 118 methods)
*(a::ValorDeriv, b::ValorDeriv) =
ValorDeriv(a.val*b.val, a.val*b.der + a.der*b.val)
* (generic function with 119 methods)
El lugar en el cual evalúo las funciones queda codificado en x
:
x = ValorDeriv(2, 1) # el lugar es el x_0=2
ValorDeriv(2,1)
p(x) = x*x + 2*x
p (generic function with 1 method)
p(1)
3
p(1.5)
5.25
p(x)
ValorDeriv(8,6)
Este resultado contiene $(p(x_0), p'(x_0))$ --es decir, ¡¡se calculó automáticamente la derivada!!
import Base.sin
sin(a::ValorDeriv) = ValorDeriv(sin(a.val), cos(a.val)*a.der)
sin (generic function with 12 methods)
f(x) = sin(x)
f (generic function with 1 method)
f(x)
ValorDeriv(0.9092974268256817,0.4161468365471424)
g(x) = sin(x*x + 3x)
g (generic function with 1 method)
g(x)
ValorDeriv(-0.5440211108893698,-5.873500703535167)
sin(2*2)
-0.7568024953079282
cos(2*2) * 2*2
-2.6145744834544478
h(x) = sin(sin(x*x + 3x))
h (generic function with 1 method)
h(x)
ValorDeriv(-0.5175807674647733,-5.025568985012155)
import Base.show
show(io::IO, a::ValorDeriv) = print(io::IO, "Función con valor $(a.val) y
derivada $(a.der)")
show (generic function with 92 methods)
h(x)
Función con valor -0.5175807674647733 y derivada -5.025568985012155
show(io::IO, a::ValorDeriv) = print(io::IO, "[$(a.val), $(a.der)]")
show (generic function with 92 methods)
h(x)
[-0.5175807674647733, -5.025568985012155]
z = h(x)
@sprintf "%.5f" z.val
"-0.51758"
show(io::IO, a::ValorDeriv) = print(io::IO, "[$(@sprintf "%.5f" a.val), $(a.der)]")
show (generic function with 92 methods)
h(x)
[-0.51758, -5.025568985012155]
"0.1"