def update_boundary_conditions(self):
        """
        Update the boundary conditions for new wall location and new wall temperature.
        """

        # Update the thermal boundary condition at the wall based on the current concentration
        Tf_wall_last = self.Tf_wall
        self.Tf = Tf_depression(self.C,linear=True)/abs(self.T_inf)
        self.C_wall = dolfin.project(self.C,self.sol_V).vector()[self.sol_idx_wall]
        Tf_wall_hold = Tf_depression(self.C_wall,linear=True)/abs(self.T_inf)
        self.Tf_wall = Tf_wall_last + (Tf_wall_hold-Tf_wall_last)*self.Tf_reg
        # Reset ice boundary condition
        self.bc_iWall = dolfin.DirichletBC(self.ice_V, self.Tf_wall, self.iWall)
        # Reset solution boundary condition
        self.bc_sWall = dolfin.DirichletBC(self.sol_V, self.Tf_wall, self.sWall)
    def solve_molecular(self,rho_solute=const.rhom):
        """
        Solve the molecular diffusion problem
        if 'solve_sol_mol' is not in the flags list this will be an instantaneous mixing problem.
        """

        # Solve solution concentration
        # Diffusivity
        ramp = dolfin.Expression('x[0] > minR ? 100. : 1.',minR=np.log(self.Rstar)-0.5,degree=1)
        self.Dstar = dolfin.project(dolfin.Expression('ramp*diff_ratio*exp(-2.*x[0])',degree=1,ramp=ramp,diff_ratio=self.astar_i/self.Lewis),self.sol_V)
        # calculate solute flux (this is like the Stefan condition for the molecular diffusion problem
        Dwall = self.Dstar(self.sol_coords[self.sol_idx_wall])
        self.solFlux = -(self.C_wall/Dwall)*(self.dR/self.dt)
        # Set the concentration for points that moved out of the grid to match the solute flux
        u0_c_hold = self.u0_c.vector()[:].copy()
        u0_c_hold[~self.sol_idx_extrapolate] = self.C_wall+self.solFlux*(np.exp(self.sol_coords[~self.sol_idx_extrapolate,0])-self.Rstar)
        u0_c_hold[u0_c_hold<0.]=0.
        self.u0_c.vector()[:] = u0_c_hold[:]
        # Variational Problem
        F_c = (self.u_s-self.u0_c)*self.v_s*dolfin.dx + \
                self.dt*dolfin.inner(dolfin.grad(self.u_s), dolfin.grad(self.Dstar*self.v_s))*dolfin.dx - \
                self.dt*self.solFlux*self.v_s*self.sds(1)
        F_c = dolfin.action(F_c,self.C)
        # First derivative
        J = dolfin.derivative(F_c, self.C, self.u_s)
        # handle the bounds
        lower = dolfin.project(dolfin.Constant(0.0),self.sol_V)
        upper = dolfin.project(dolfin.Constant(rho_solute),self.sol_V)
        # set bounds and solve
        snes_solver_parameters = {"nonlinear_solver": "snes",
                                  "snes_solver": {"linear_solver": "lu",
                                                  "maximum_iterations": 20,
                                                  "report": True,
                                                  "error_on_nonconvergence": False}}
        problem = dolfin.NonlinearVariationalProblem(F_c, self.C, J=J)
        problem.set_bounds(lower, upper)
        solver = dolfin.NonlinearVariationalSolver(problem)
        solver.parameters.update(snes_solver_parameters)
        dolfin.info(solver.parameters, True)
        (iter, converged) = solver.solve()
        self.u0_c.assign(self.C)

        # Recalculate the freezing temperature
        self.Tf_last = self.Tf
        self.Tf = Tf_depression(self.C.vector()[self.sol_idx_wall,0],linear=True)
        # Get the updated solution properties
        self.rhos = dolfin.project(dolfin.Expression('C + rhow*(1.-C/rho_solute)',
            degree=1,C=self.C,rhow=const.rhow,rho_solute=rho_solute),self.sol_V)
        self.cs = dolfin.project(dolfin.Expression('ce*(C/rho_solute) + cw*(1.-C/rho_solute)',
            degree=1,C=self.C,cw=const.cw,ce=const.ce,rho_solute=rho_solute),self.sol_V)
        self.ks = dolfin.project(dolfin.Expression('ke*(C/rho_solute) + kw*(1.-C/rho_solute)',
            degree=1,C=self.C,kw=const.kw,ke=const.ke,rho_solute=rho_solute),self.sol_V)
        self.rhos_wall = self.rhos.vector()[self.sol_idx_wall]
        self.cs_wall = self.cs.vector()[self.sol_idx_wall]
        self.ks_wall = self.ks.vector()[self.sol_idx_wall]
    def run(self,verbose=False,initialize_array=True):
        """
        Iterate the model through the given time array.
        """

        if initialize_array:
            self.r_ice_result = [np.exp(self.ice_coords[:,0])*self.R_melt]
            self.T_ice_result = [np.array(self.u0_i.vector()[:]*abs(self.T_inf))]
            self.r_sol_result = [np.exp(self.sol_coords[:,0])*self.R_melt]
            self.T_sol_result = [np.array(self.u0_s.vector()[:]*abs(self.T_inf))]
            self.Tf_result = [Tf_depression(np.array(self.u0_c.vector()[:]),linear=True)]
        for i,t in enumerate(self.ts[1:]):
            if verbose:
                print(round(t*self.t0/60.),end=' min, ')

            # --- Ethanol Injection --- #
            self.injection_energy_balance(self.source[i+1])

            # --- Thermal Diffusion --- #
            self.update_boundary_conditions()
            self.solve_thermal()

            # --- Move the Mesh --- #
            self.move_wall()
            # break the loop if the hole is completely frozen
            if 'Frozen' in self.flags:
                print('Frozen Hole!')
                break

            # --- Molecular Diffusion --- #
            self.solve_molecular()

            # Save the new wall location
            self.Rstar = np.exp(self.ice_coords[self.ice_idx_wall,0])

            # --- Export --- #
            if t in self.save_times:
                self.r_ice_result = np.append(self.r_ice_result,[np.exp(self.ice_coords[:,0])*self.R_melt],axis=0)
                self.T_ice_result = np.append(self.T_ice_result,[self.u0_i.vector()[:]*abs(self.T_inf)],axis=0)
                self.r_sol_result = np.append(self.r_sol_result,[np.exp(self.sol_coords[:,0])*self.R_melt],axis=0)
                self.T_sol_result = np.append(self.T_sol_result,[self.u0_s.vector()[:]*abs(self.T_inf)],axis=0)
                self.Tf_result = np.append(self.Tf_result,[Tf_depression(self.u0_c.vector()[:],linear=True)],axis=0)
    def update_boundary_conditions(self, data_dir=None):
        """
        Update the thermal boundary conditions for new wall location based on the current concentration.
        """

        # Update the melting temperature
        self.Tf = Tf_depression(self.C, data_dir=data_dir) / abs(self.T_inf)
        self.Tf_wall = self.Tf
        # Reset ice boundary condition
        self.bc_iWall = dolfin.DirichletBC(self.ice_V, self.Tf_wall,
                                           self.iWall)
    def get_initial_conditions(self, rho_solute=const.rhom, data_dir=None):
        """
        Set the initial condition at the end of melting (melting can be solved analytically)
        """

        # --- Initial states --- #
        # ice temperature
        self.u0_i = dolfin.Function(self.ice_V)
        T, lam, self.R_melt, self.t_melt = analyticalMelt(
            np.exp(self.ice_coords[:, 0]) * self.R_melt,
            self.T_inf,
            self.Q_initialize,
            R_target=self.R_melt)
        self.u0_i.vector()[:] = T / abs(self.T_inf)

        # --- Time Array --- #
        # Now that we have the melt-out time, we can define the time array
        self.ts = np.arange(self.t_melt, self.t_final + self.dt,
                            self.dt) / self.t0
        self.dt /= self.t0
        # Define the antifreeze source
        self.source_timing = self.ts[np.argmin(
            abs(self.ts - self.source_timing / self.t0))]
        if 'gaussian_source' in self.flags:
            self.source_duration /= self.t0
            self.source = self.source_mass_final / (
                self.source_duration * np.sqrt(2. * np.pi)) * np.exp(-.5 * (
                    (self.ts - self.source_timing) / self.source_duration)**2.)
        else:
            self.source = np.zeros_like(self.ts)
            self.source[np.argmin(
                abs(self.ts -
                    self.source_timing))] = self.source_mass_final / self.dt

        # --- Define the test and trial functions --- #
        self.u_i = dolfin.TrialFunction(self.ice_V)
        self.v_i = dolfin.TestFunction(self.ice_V)
        self.T_i = dolfin.Function(self.ice_V)
        self.C = self.C_init
        self.Tf_wall = Tf_depression(self.C, data_dir=data_dir)
        # Get the updated solution properties
        self.rhos = self.C + const.rhow * (1. - self.C / rho_solute)
        self.cs = (self.C /
                   rho_solute) * const.ce + (1. -
                                             (self.C / rho_solute)) * const.cw
        self.ks = (self.C /
                   rho_solute) * const.ke + (1. -
                                             (self.C / rho_solute)) * const.kw
        self.rhos_wall, self.cs_wall, self.ks_wall = self.rhos, self.cs, self.ks

        self.flags.append('get_ic')
    def get_initial_conditions(self,rho_solute=const.rhom):
        """
        Set the initial condition at the end of melting (melting can be solved analytically
        """

        # --- Initial states --- #
        # ice temperature
        self.u0_i = dolfin.Function(self.ice_V)
        T,lam,self.R_melt,self.t_melt = analyticalMelt(np.exp(self.ice_coords[:,0])*self.R_melt,self.T_inf,self.Q_initialize,R_target=self.R_melt)
        self.u0_i.vector()[:] = T/abs(self.T_inf)
        # solution temperature
        self.u0_s = dolfin.Function(self.sol_V)
        T,lam,self.R_melt,self.t_melt = analyticalMelt(np.exp(self.sol_coords[:,0])*self.R_melt,self.T_inf,self.Q_initialize,R_target=self.R_melt)
        self.u0_s.vector()[:] = T/abs(self.T_inf)
        # solution concentration
        self.u0_c = dolfin.interpolate(dolfin.Constant(self.C_init),self.sol_V)

        # --- Time Array --- #
        # Now that we have the melt-out time, we can define the time array
        self.ts = np.arange(self.t_melt,self.t_final+self.dt,self.dt)/self.t0
        self.dt /= self.t0
        # Define the ethanol source
        self.source_timing = self.ts[np.argmin(abs(self.ts-self.source_timing/self.t0))]
        if 'gaussian_source' in self.flags:
            self.source_duration /= self.t0
            self.source = self.source_mass_final/(self.source_duration*np.sqrt(np.pi))*np.exp(-((self.ts-self.source_timing)/self.source_duration)**2.)
        else:
            self.source = np.zeros_like(self.ts)
            self.source[np.argmin(abs(self.ts-self.source_timing))] = self.source_mass_final/self.dt

        # --- Define the test and trial functions --- #
        self.u_i = dolfin.TrialFunction(self.ice_V)
        self.v_i = dolfin.TestFunction(self.ice_V)
        self.T_i = dolfin.Function(self.ice_V)
        self.u_s = dolfin.TrialFunction(self.sol_V)
        self.v_s = dolfin.TestFunction(self.sol_V)
        self.T_s = dolfin.Function(self.sol_V)
        self.C = dolfin.Function(self.sol_V)
        self.Tf = Tf_depression(self.C,linear=True)/abs(self.T_inf)
        self.Tf_wall = dolfin.project(self.Tf,self.sol_V).vector()[self.sol_idx_wall]
        # Get the updated solution properties
        self.rhos = dolfin.project(dolfin.Expression('C + rhow*(1.-C/rho)',degree=1,C=self.C,rhow=const.rhow,rho=rho_solute),self.sol_V)
        self.cs = dolfin.project(dolfin.Expression('ce*(C/rho) + cw*(1.-C/rho)',degree=1,C=self.C,cw=const.cw,ce=const.ce,rho=rho_solute),self.sol_V)
        self.ks = dolfin.project(dolfin.Expression('ke*(C/rho) + kw*(1.-C/rho)',degree=1,C=self.C,kw=const.kw,ke=const.ke,rho=rho_solute),self.sol_V)
        self.rhos_wall = self.rhos.vector()[self.sol_idx_wall]
        self.cs_wall = self.cs.vector()[self.sol_idx_wall]
        self.ks_wall = self.ks.vector()[self.sol_idx_wall]

        self.flags.append('get_ic')
    def injection_energy_balance(self,
                                 source,
                                 solute='methanol',
                                 data_dir=None):
        """
        Update the concentration and temperature at the time of injection.
        """

        # Calculate the injection source actually adds to total concentration
        C_inject = source * self.dt / (np.pi * (self.Rstar * self.R_melt)**2.)
        # Hard set on concentration (assume that it mixes quickly)
        self.C += C_inject

        # enthalpy of mixing, always exothermic so gives off energy (J m-3)
        H, phi = Hmix(C_inject, solute=solute)
        # put this added energy toward uniformly warming the solution
        phi_dT = -phi / (self.rhos * self.cs) / abs(self.T_inf)

        # Hard set on solution temperature (assume that the mixing energy spreads evenly)
        self.Tf = Tf_depression(self.C, data_dir=data_dir) / abs(self.T_inf)
        # Bump the last freezing temperature up
        # this way the mixing enthalpy is accounted for in wall movement
        self.Tf_last = (Tf_depression(self.C - C_inject, data_dir=data_dir) +
                        phi_dT) / abs(self.T_inf)
    def solve_molecular(self, rho_solute=const.rhom, data_dir=None):
        """
        Update the solution concentration depending on how far the hole wall moved.
        """

        # Recalculate the solution concentration after wall moves
        self.C *= (self.Rstar /
                   np.exp(self.ice_coords[self.ice_idx_wall, 0]))**2.
        # Recalculate the freezing temperature
        self.Tf_last = self.Tf
        self.Tf = Tf_depression(self.C, data_dir=data_dir)
        # Get the updated solution properties
        self.rhos = self.C + const.rhow * (1. - self.C / rho_solute)
        self.cs = (self.C /
                   rho_solute) * const.ce + (1. -
                                             (self.C / rho_solute)) * const.cw
        self.ks = (self.C /
                   rho_solute) * const.ke + (1. -
                                             (self.C / rho_solute)) * const.kw
        self.rhos_wall, self.cs_wall, self.ks_wall = self.rhos, self.cs, self.ks
    def initiate_solution_diffusion(mod,rho_solute=const.rhom):
        """
        Before the equations can be solved for the liquid solution: initial conditions and solution properties need to be setup
            update domain
            time array and ethanol source timing
            initial conditions
            variational problem
            solution properties
            boundary conditions
        To be run once after get_initial_conditions() and get_boundary_conditions()
        """

        # --- Get new domain --- #
        mod.sol_mesh = dolfin.IntervalMesh(mod.n,mod.Rstar_center,mod.Rstar)
        mod.sol_V = dolfin.FunctionSpace(mod.sol_mesh,'CG',1)
        mod.sol_mesh.coordinates()[:] = np.log(mod.sol_mesh.coordinates())
        mod.sol_coords = mod.sol_V.tabulate_dof_coordinates().copy()
        mod.sol_idx_wall = np.argmax(mod.sol_coords)

        # --- Time array --- #
        # Now that we have the melt-out time, we can define the time array
        mod.ts = np.arange(mod.t_init,mod.t_final+mod.dt,mod.dt)/mod.t0
        mod.dt /= mod.t0
        # Define the ethanol source
        mod.source_timing = mod.source_timing/mod.t0
        mod.source_duration /= mod.t0
        mod.source = mod.source_mass_final/(mod.source_duration*np.sqrt(np.pi))*np.exp(-((mod.ts-mod.source_timing)/mod.source_duration)**2.)

        # --- Set initial conditions --- #
        # solution temperature
        mod.u0_s = dolfin.Function(mod.sol_V)
        mod.u0_s.vector()[:] = mod.Tf_wall
        # solution concentration
        mod.u0_c = dolfin.project(dolfin.Constant(mod.C),mod.sol_V)

        # --- Set up the variational Problem --- #
        mod.u_s = dolfin.TrialFunction(mod.sol_V)
        mod.v_s = dolfin.TestFunction(mod.sol_V)
        mod.T_s = dolfin.Function(mod.sol_V)
        mod.C = dolfin.project(dolfin.Constant(mod.C),mod.sol_V)
        mod.Tf = Tf_depression(mod.C,linear=True)/abs(mod.T_inf)
        mod.Tf_wall = dolfin.project(mod.Tf,mod.sol_V).vector()[mod.sol_idx_wall]

        # --- Get the solution properties --- #
        mod.rhos = dolfin.project(dolfin.Expression('C + rhow*(1.-C/rho_solute)',degree=1,C=mod.C,rhow=const.rhow,rho_solute=rho_solute),mod.sol_V)
        mod.cs = dolfin.project(dolfin.Expression('ce*(C/rho_solute) + cw*(1.-C/rho_solute)',degree=1,C=mod.C,cw=const.cw,ce=const.ce,rho_solute=rho_solute),mod.sol_V)
        mod.ks = dolfin.project(dolfin.Expression('ke*(C/rho_solute) + kw*(1.-C/rho_solute)',degree=1,C=mod.C,kw=const.kw,ke=const.ke,rho_solute=rho_solute),mod.sol_V)
        mod.rhos_wall = mod.rhos.vector()[mod.sol_idx_wall]
        mod.cs_wall = mod.cs.vector()[mod.sol_idx_wall]
        mod.ks_wall = mod.ks.vector()[mod.sol_idx_wall]

        # --- Boundary Conditions --- #
        # Liquid boundary condition at hole wall (same temperature as ice)
        class sWall(dolfin.SubDomain):
            def inside(self, x, on_boundary):
                return on_boundary and x[0] > mod.sol_coords[mod.sol_idx_wall] - const.tol
        # center flux
        class center(dolfin.SubDomain):
            def inside(self, x, on_boundary):
                return on_boundary and x[0] < mod.w_center + const.tol

        # Initialize boundary classes
        mod.sWall = sWall()
        mod.center = center()
        # This will be used in the boundary condition for mass diffusion
        mod.boundaries = dolfin.MeshFunction("size_t", mod.sol_mesh, 0) # this index 0 is an alternative to the command boundaries.set_all(0)
        mod.sWall.mark(mod.boundaries, 1)
        mod.center.mark(mod.boundaries, 2)
        mod.sds = dolfin.Measure("ds")(subdomain_data=mod.boundaries)