Source code for pyqchem.qc_input
import numpy as np
from copy import deepcopy
from pyqchem.basis import basis_to_txt, get_purecard
import hashlib, json
import warnings
from pyqchem.errors import QchemInputWarning, QchemInputError
warnings.filterwarnings("default", category=DeprecationWarning) # make DeprecationWarning visible
[docs]def normalize_values(value):
"""
Set all string values (including keys and values of dictionaries) to lower case
:param value: the values
:return: normalized values
"""
def normalize(value):
if isinstance(value, str):
return value.lower()
return value
if isinstance(value, dict):
return {normalize(k): normalize(v) for k, v in value.items()}
else:
return normalize(value)
[docs]class QchemInput:
"""
Handles the Q-Chem input info
"""
def __init__(self, molecule,
jobtype='sp',
method=None,
exchange=None,
correlation=None,
unrestricted=None,
basis='6-31G',
basis2=None,
thresh=14,
scf_convergence=8,
max_scf_cycles=50,
scf_algorithm='diis',
purecart=None,
# RASCI
ras_roots=None,
ras_do_hole=True,
ras_do_part=True,
ras_act=None,
ras_act_orb=None,
ras_elec=None,
ras_elec_alpha=None,
ras_elec_beta=None,
ras_occ=None,
ras_spin_mult=1,
ras_sts_tm=False,
ras_fod=False,
ras_natorb=False,
ras_natorb_state=None,
ras_print=1,
ras_diabatization_scheme=None,
ras_diabatization_states=None,
ras_guess=None,
use_reduced_ras_guess=False,
# RASCI SrDFT
ras_omega=400,
ras_srdft=None,
ras_srdft_damp=0.5,
ras_srdft_exc=None,
ras_srdft_cor=None,
ras_srdft_spinpol=0,
# SOC
calc_soc=False,
state_analysis=False,
# EOM
ee_singlets=False,
ee_triplets=False,
cc_trans_prop=False,
cc_symmetry=True,
cc_e_conv=None,
cc_t_conv=None,
eom_davidson_conv=5,
# CIS
cis_convergence=6,
cis_n_roots=None,
cis_singlets=False,
cis_triplets=False,
cis_ampl_anal=False,
loc_cis_ov_separate=False,
er_cis_numstate=0,
boys_cis_numstate=0,
cis_diabath_decompose=False,
max_cis_cycles=30,
localized_diabatization=None,
sts_multi_nroots=None,
cc_state_to_opt=None,
cis_state_deriv=None,
RPA=False,
set_iter=30,
gui=0,
# optimization
geom_opt_dmax=300,
geom_opt_update=-1,
geom_opt_linear_angle=165,
geom_opt_coords=-1,
geom_opt_tol_gradient=300,
geom_opt_tol_displacement=1200,
geom_opt_tol_energy=100,
geom_opt_max_cycles=50,
geom_opt_constrains=None,
# solvent
solvent_method=None,
solvent_params=None,
pcm_params=None,
# IRC
rpath_coords=0,
rpath_direction=1,
rpath_max_cycles=20,
rpath_max_stepsize=150,
rpath_tol_displacement=5000,
# symmetry
symmetry=True,
sym_ignore=False,
# other
nto_pairs=None,
n_frozen_core=None,
n_frozen_virt=None, # to be deprecated in the future
n_frozen_virtual=0,
mom_start=False,
reorder_orbitals=None,
namd_nsurfaces=None,
scf_print=None,
scf_guess=None,
scf_energies=None,
scf_density=None,
scf_guess_mix=False,
hessian=None,
sym_tol=5,
mem_total=2000,
mem_static=64,
skip_scfman=False,
# special
extra_rem_keywords=None,
extra_sections=None,
):
# put to arguments self._* (will be written explicitly)
self.purecart = purecart
for name, value in vars().items():
if name != 'self':
# set keywords in lower case
value = normalize_values(value)
setattr(self, '_' + name, value)
# set ras_occ
if ras_roots is not None:
if exchange is None:
self._exchange = 'hf'
if ras_occ is None:
if ras_elec is not None:
self._ras_occ = (np.sum(
molecule.get_atomic_numbers()) - ras_elec - molecule.charge) // 2
elif ras_elec_alpha is not None or ras_elec_beta is not None:
self._ras_occ = (np.sum(
molecule.get_atomic_numbers()) - ras_elec_alpha - ras_elec_beta - molecule.charge) // 2
else:
self._ras_occ = (np.sum(molecule.get_atomic_numbers()) - molecule.charge) // 2
self._ras_occ = int(self._ras_occ)
warnings.warn(QchemInputWarning('set ras_occ = {}'.format(self._ras_occ)))
# Handle custom basis set
if type(basis) is not str:
self._basis = 'gen'
self._custom_basis = basis
self._purecart = get_purecard(basis)
# Handle custom basis set
if not isinstance(basis2, str) and basis2 is not None:
self._basis2 = 'gen'
self._custom_basis2 = basis2
self._purecart = get_purecard(basis2)
# Handle cc_trans_prop
if isinstance(self._cc_trans_prop, dict):
self._trans_prop = self._cc_trans_prop
self._cc_trans_prop = 1
else:
self._trans_prop = None
# Handle reorder mo
if self._reorder_orbitals is not None:
if not 'beta' in self._reorder_orbitals:
self._reorder_orbitals['beta'] = self._reorder_orbitals['alpha']
# handle explicit guess (from MO coefficients)
if scf_guess is not None and type(scf_guess) is not str:
# print('set custom guess')
self._scf_guess = 'read'
self._mo_coefficients = scf_guess
else:
self._mo_coefficients = None
if self._ras_srdft is not None:
from warnings import warn
warn('Warning! ras_srdft keyword is deprecated, this will be automatically '
'activated when using ras_srdft_exc and ras_srdft_cor')
if ras_srdft_exc is not None or ras_srdft_cor is not None:
self._ras_srdft = True
else:
self._ras_srdft = False
def __hash__(self):
# take all keywords defined in input
keywords = dict(self.__dict__)
# remove keywords that not affect the results (these keywords will be ignored in hash)
for key in ['_mem_total', '_mem_static', '_gui', '_set_iter', '_max_scf_cycles',
'_geom_opt_max_cycles', '_max_cis_cycles']:
keywords.pop(key, None)
# Change molecule object by molecule coordinates (Structure class too complex for JSON)
keywords['_molecule'] = hash(keywords['_molecule'])
# Handle extra section
if keywords['_extra_sections'] is not None:
if isinstance(keywords['_extra_sections'], list):
keywords['_extra_sections'] = tuple(keywords['_extra_sections'])
keywords['_extra_sections'] = hash(keywords['_extra_sections'],)
digest = hashlib.md5(json.dumps(keywords, sort_keys=True).encode()).hexdigest()
return int(digest, 16)
[docs] def get_txt(self):
"""
get qchem input in plain text
:return string: qchem input in plain text
"""
input_file = ''
##############################
# Molecule definition
##############################
input_file += '$molecule\n'
input_file += '{} {}\n'.format(self._molecule.charge, self._molecule.multiplicity)
atomic_elements = self._molecule.get_symbols()
coordinates = self._molecule.get_coordinates()
for index, element in enumerate(atomic_elements):
input_file += (element + '\t' + '{:20.10f} {:20.10f} {:20.10f}\n'.format(*coordinates[index]))
input_file += '$end\n'
#####################
# Rem variables
#####################
input_file += '$rem\n'
input_file += 'jobtype {}\n'.format(self._jobtype)
if self._exchange is not None:
input_file += 'exchange {}\n'.format(self._exchange)
if self._method:
input_file += 'method {}\n'.format(self._method)
if self._n_frozen_virt is not None:
self._n_frozen_virtual = self._n_frozen_virt
warnings.warn('"n_frozen_virt" keyword will be deprecated. Use "n_frozen_virtual" instead', DeprecationWarning)
input_file += 'basis {}\n'.format(self._basis)
input_file += 'thresh {}\n'.format(self._thresh)
input_file += 'scf_convergence {}\n'.format(self._scf_convergence)
input_file += 'scf_algorithm {}\n'.format(self._scf_algorithm)
input_file += 'max_scf_cycles {}\n'.format(self._max_scf_cycles)
input_file += 'gui {}\n'.format(self.gui)
input_file += 'set_iter {}\n'.format(self._set_iter)
input_file += 'RPA {}\n'.format(self._RPA)
input_file += 'mem_total {}\n'.format(self._mem_total)
input_file += 'mem_static {}\n'.format(self._mem_static)
input_file += 'n_frozen_virtual {}\n'.format(self._n_frozen_virtual)
input_file += 'mom_start {}\n'.format(self._mom_start)
input_file += 'skip_scfman {}\n'.format(self._skip_scfman)
input_file += 'scf_guess_mix {}\n'.format(self._scf_guess_mix)
if self._basis2 is not None:
input_file += 'basis2 {}\n'.format(self._basis2)
if self._n_frozen_core is not None:
input_file += 'n_frozen_core {}\n'.format(self._n_frozen_core)
if self._solvent_method is not None:
input_file += 'solvent_method {}\n'.format(self._solvent_method)
if self._unrestricted is not None:
input_file += 'unrestricted {}\n'.format(self._unrestricted)
if self._purecart is not None:
input_file += 'purecart {}\n'.format(self._purecart)
if self._correlation is not None:
input_file += 'correlation {}\n'.format(self._correlation)
# RasCI variables
if self._ras_roots is not None:
if self._ras_natorb_state is not None:
input_file += 'ras_natorb_state {}\n'.format(self._ras_natorb_state)
self._ras_natorb = True
input_file += 'ras_roots {}\n'.format(self._ras_roots)
input_file += 'ras_do_hole {}\n'.format(self._ras_do_hole)
input_file += 'ras_do_part {}\n'.format(self._ras_do_part)
input_file += 'ras_occ {}\n'.format(self._ras_occ)
input_file += 'ras_spin_mult {}\n'.format(self._ras_spin_mult)
input_file += 'ras_print {}\n'.format(self._ras_print)
input_file += 'ras_natorb {}\n'.format(self._ras_natorb)
input_file += 'ras_sts_tm {}\n'.format(self._ras_sts_tm)
input_file += 'ras_fod {}\n'.format(self._ras_fod)
# input_file += 'max_cis_cycles {}\n'.format(self._max_cis_cycles)
# input_file += 'RAS_RESTR_TYPE {}\n'.format(True)
if self._ras_elec is not None:
input_file += 'ras_elec {}\n'.format(self._ras_elec)
else:
if self._ras_elec_alpha is None and self._ras_elec_beta is None:
raise QchemInputError('{} not defined'.format('ras_elec'))
if self._ras_elec_alpha is not None:
input_file += 'ras_elec_alpha {}\n'.format(self._ras_elec_alpha)
if self._ras_elec_beta is not None:
input_file += 'ras_elec_beta {}\n'.format(self._ras_elec_beta)
if self._ras_act is not None:
input_file += 'ras_act {}\n'.format(self._ras_act)
else:
raise QchemInputError('{} not defined'.format('ras_act'))
if self._ras_act_orb is not None:
input_file += 'ras_act_orb [' + ','.join([str(num) for num in self._ras_act_orb]) + ']\n'
# Sr-DFT
if self._ras_srdft:
input_file += 'ras_srdft {}\n'.format('True')
input_file += 'ras_srdft_damp {}\n'.format(self._ras_srdft_damp)
input_file += 'ras_srdft_spinpol {}\n'.format(self._ras_srdft_spinpol)
input_file += 'ras_omega {}\n'.format(self._ras_omega)
if self._ras_srdft_exc is not None:
input_file += 'ras_srdft_exc {}\n'.format(self._ras_srdft_exc)
if self._ras_srdft_cor is not None:
input_file += 'ras_srdft_cor {}\n'.format(self._ras_srdft_cor)
else:
raise QchemInputError('{} not defined'.format('ras_srdft_cor'))
# Diabatization
diab_methods = {'ER': 1, 'Boys': 2, 'DQ': 3, 'Gamma': 4}
if self._ras_diabatization_states is not None:
input_file += 'sts_multi_nroots {}\n'.format(len(self._ras_diabatization_states))
input_file += 'cis_diabath_decompose {}\n'.format(len(self._ras_diabatization_scheme))
input_file += 'ras_diab_seq_data ['
for seq in self._ras_diabatization_scheme:
input_file += '{} '.format([num for num in seq['states']] +
[diab_methods[seq['method']]] +
[seq['parameters'] if 'parameters' in seq else 0.0]).replace(' ', '')[1:-1]
input_file += ','
input_file = input_file[:-1] + ']\n'
input_file += 'ras_diab_seq_list ' + '{}\n'.format([len(seq['states']) for seq in self._ras_diabatization_scheme]).replace(' ', '')
# RAS guess
if self._ras_guess is not None:
if self._use_reduced_ras_guess:
input_file += 'set_redo {}\n'.format(1)
else:
input_file += 'set_redo {}\n'.format(2)
# Borrowed keywords
input_file += 'cis_convergence {}\n'.format(self._cis_convergence)
#if self._method.upper() in ['EOM-CCSD'] or self._correlation.upper() in ['CCSD']:
if self._method is not None:
# EOM
if self._method.upper() in ['EOM-CCSD']:
input_file += 'cc_trans_prop {}\n'.format(self._cc_trans_prop)
input_file += 'cc_symmetry {}\n'.format(self._cc_symmetry)
if self._cc_e_conv is not None:
input_file += 'cc_e_conv {}\n'.format(self._cc_e_conv)
if self._cc_t_conv is not None:
input_file += 'cc_t_conv {}\n'.format(self._cc_t_conv)
if self._ee_singlets is not False:
input_file += 'ee_singlets [' + ','.join([str(num) for num in self._ee_singlets]) + ']\n'
if self._ee_triplets is not False:
input_file += 'ee_triplets [' + ','.join([str(num) for num in self._ee_triplets]) + ']\n'
input_file += 'eom_davidson_conv {}\n'.format(self._eom_davidson_conv)
# SOC
if self._calc_soc is not False:
input_file += 'calc_soc {}\n'.format(self._calc_soc)
if self._state_analysis is not False:
input_file += 'state_analysis {}\n'.format(self._state_analysis)
# CIS variables
if self._cis_n_roots is not None:
input_file += 'cis_convergence {}\n'.format(self._cis_convergence)
input_file += 'cis_n_roots {}\n'.format(self._cis_n_roots)
input_file += 'cis_singlets {}\n'.format(self._cis_singlets)
input_file += 'cis_triplets {}\n'.format(self._cis_triplets)
input_file += 'cis_ampl_anal {}\n'.format(self._cis_ampl_anal)
input_file += 'loc_cis_ov_separate {}\n'.format(self._loc_cis_ov_separate)
input_file += 'er_cis_numstate {}\n'.format(self._er_cis_numstate)
input_file += 'boys_cis_numstate {}\n'.format(self._boys_cis_numstate)
input_file += 'max_cis_cycles {}\n'.format(self._max_cis_cycles)
# other
if self._namd_nsurfaces is not None:
input_file += 'namd_nsurfaces {}\n'.format(self._namd_nsurfaces)
if self._sts_multi_nroots is not None:
input_file += 'sts_multi_nroots {}\n'.format(self._sts_multi_nroots)
if self._localized_diabatization is not None:
input_file += 'cis_diabath_decompose {}\n'.format(self._cis_diabath_decompose)
if self._cc_state_to_opt is not None:
input_file += 'cc_state_to_opt [{},{}]\n'.format(self._cc_state_to_opt[0], self._cc_state_to_opt[1])
if self._cis_state_deriv is not None:
input_file += 'cis_state_deriv {}\n'.format(self._cis_state_deriv)
if self._scf_print is not None:
input_file += 'scf_print {}\n'.format(self._scf_print)
if self._scf_guess is not None:
input_file += 'scf_guess {}\n'.format(self._scf_guess)
if self._symmetry is not None:
input_file += 'symmetry {}\n'.format(self._symmetry)
if self._sym_ignore is not None:
input_file += 'sym_ignore {}\n'.format(self._sym_ignore)
if self._sym_tol is not None:
input_file += 'sym_tol {}\n'.format(self._sym_tol)
if self._nto_pairs is not None:
input_file += 'nto_pairs {}\n'.format(self._nto_pairs)
# optimization
if self._jobtype.lower() in ['opt', 'ts']:
input_file += 'geom_opt_dmax {}\n'.format(self._geom_opt_dmax)
input_file += 'geom_opt_update {}\n'.format(self._geom_opt_update)
input_file += 'geom_opt_linear_angle {}\n'.format(self._geom_opt_linear_angle)
input_file += 'geom_opt_coords {}\n'.format(self._geom_opt_coords)
input_file += 'geom_opt_tol_gradient {}\n'.format(self._geom_opt_tol_gradient)
input_file += 'geom_opt_tol_displacement {}\n'.format(self._geom_opt_tol_displacement)
input_file += 'geom_opt_tol_energy {}\n'.format(self._geom_opt_tol_energy)
input_file += 'geom_opt_max_cycles {}\n'.format(self._geom_opt_max_cycles)
# IRC
if self._jobtype.lower() in ['rpath']:
input_file += 'rpath_coords {}\n'.format(self._rpath_coords)
input_file += 'rpath_direction {}\n'.format(self._rpath_direction)
input_file += 'rpath_max_cycles {}\n'.format(self._rpath_max_cycles)
input_file += 'rpath_max_stepsize {}\n'.format(self._rpath_max_stepsize)
input_file += 'rpath_tol_displacement {}\n'.format(self._rpath_tol_displacement)
# Extra keywords
if self._extra_rem_keywords is not None:
for key, value in self._extra_rem_keywords.items():
input_file += '{} {}\n'.format(key, value)
input_file += '$end\n'
##########################
# Additional sections
##########################
# localized diabatization
if self._localized_diabatization is not None:
input_file += '$localized_diabatization\nadiabatic states\n'
input_file += ' '.join(np.array(self._localized_diabatization, dtype=str))
input_file += '\n$end\n'
# Constrains section
def modulate_angles(type, value):
if type in ['tors', 'outp', 'linc', 'linp', 'bend']:
value = np.mod(value + 180, 360) - 180
if type in ['bend']:
value = np.abs(value)
return value
if self._geom_opt_constrains is not None:
input_file += '$opt\n'
input_file += 'CONSTRAINT\n'
for type, constrains in self._geom_opt_constrains.items():
for constrain in constrains:
input_file += '{} {} {:15.6f}\n'.format(type,
' '.join([str(num) for num in constrain['atoms']]),
modulate_angles(type, constrain['value']))
input_file += 'ENDCONSTRAINT\n'
input_file += '$end\n'
# Diabatization section
if self._ras_diabatization_states is not None:
input_file += '$localized_diabatization\n'
input_file += 'adiabatic states\n'
input_file += ' '.join([str(num) for num in self._ras_diabatization_states])
input_file += '\n$end\n'
# custom basis section
if self._basis == 'gen':
input_file += '$basis\n'
input_file += basis_to_txt(self._custom_basis)
input_file += '$end\n'
# custom basis2 section
if self._basis2 == 'gen':
input_file += '$basis2\n'
input_file += basis_to_txt(self._custom_basis2)
input_file += '$end\n'
# reorder orbitals
if self._reorder_orbitals is not None:
input_file += '$reorder_mo\n'
input_file += ' '.join([str(s) for s in self._reorder_orbitals['alpha']]) + '\n'
input_file += ' '.join([str(s) for s in self._reorder_orbitals['beta']]) + '\n'
input_file += '$end\n'
# trans_prop section
if self._trans_prop is not None:
input_file += '$trans_prop\n'
if 'state_list' in self._trans_prop:
input_file += 'state_list\n'
if 'ee_singlets' in self._trans_prop['state_list']:
for pair in self._trans_prop['state_list']['ee_singlets']:
input_file += 'ee_singlets {} {}\n'.format(*pair)
if 'ee_triplets' in self._trans_prop['state_list']:
for pair in self._trans_prop['state_list']['ee_triplets']:
input_file += 'ee_triplets {} {}\n'.format(*pair)
if 'ref' in self._trans_prop['state_list']:
input_file += 'ref {}\n'.format(self._trans_prop['state_list']['ref'])
input_file += 'end_list\n'
if 'state_pair_list' in self._trans_prop:
for pair in self._trans_prop['state_pair_list']['ee_singlets']:
input_file += '{} {}\n'.format(*pair)
input_file += 'end_pairs\n'
if 'calc' in self._trans_prop:
for calc in self._trans_prop['calc']:
input_file += 'CALC {}\n'.format(calc)
if self._calc_soc is not False and self._calc_soc != 0:
input_file += 'CALC soc\n'
input_file += '$end\n'
# solvent
if self._solvent_params is not None:
input_file += '$solvent\n'
for prop, value in self._solvent_params.items():
input_file += '{} {}\n'.format(prop, value)
input_file += '$end\n'
if self._pcm_params is not None:
input_file += '$pcm\n'
for prop, value in self._pcm_params.items():
input_file += '{} {}\n'.format(prop, value)
input_file += '$end\n'
# extra sections
if self._extra_sections is not None:
if isinstance(self._extra_sections, list):
for section in self._extra_sections:
input_file += section.get_txt()
else:
# Only one section
input_file += self._extra_sections.get_txt()
return input_file + "\n"
def store_mo_file(self, path='.'):
guess_coeff = self._mo_coefficients
guess_energies = self._scf_energies
# set guess in place
mo_coeffa = np.array(guess_coeff['alpha'], dtype=float)
if 'beta' in guess_coeff:
mo_coeffb = np.array(guess_coeff['beta'], dtype=float)
else:
mo_coeffb = mo_coeffa
if 'qchem_order' in guess_coeff:
indices = guess_coeff['qchem_order']
reverse_indices = [list(indices).index(j) for j in range(len(indices))]
mo_coeffa = mo_coeffa[:, reverse_indices]
mo_coeffb = mo_coeffb[:, reverse_indices]
if guess_energies is not None:
mo_enea = np.array(guess_energies['alpha'], dtype=float)
if 'beta' in guess_coeff:
mo_coeffb = np.array(guess_coeff['beta'], dtype=float)
mo_eneb = np.array(guess_energies['beta'], dtype=float)
else:
mo_eneb = mo_enea
else:
# energies set with increasing values for q-chem to keep the order of orbitals
mo_enea = list(range(len(mo_coeffa)))
mo_eneb = list(range(len(mo_coeffb)))
guess_file = np.vstack([mo_coeffa, mo_coeffb, mo_enea, mo_eneb]).flatten()
with open(path + '/53.0', 'w') as f:
guess_file.tofile(f, sep='')
def store_density_file(self, path='.'):
guess_density = self._scf_density
# set guess in place
density_alpha = np.array(guess_density['alpha'], dtype=np.float)
if 'beta' in guess_density:
density_beta = np.array(guess_density['beta'], dtype=np.float)
else:
density_beta = density_alpha
guess_file = np.vstack([density_alpha, density_beta]).flatten()
with open(path + '/54.0', 'w') as f:
guess_file.tofile(f, sep='')
def store_energy_file(self, path='.'):
energy_file = np.zeros(12)
warnings.warn('warning: FILE_ENERGY will be set to zeros, this may affect post HF methods')
with open(path + '/99.0', 'w') as f:
energy_file.tofile(f, sep='')
def store_hessian_file(self, path='.'):
hessian_triu = np.array(self._hessian)
with open(path + '/132.0', 'w') as f:
hessian_triu.tofile(f, sep='')
def store_ras_guess_file(self, path='.'):
ras_guess_file = np.array(self._ras_guess, dtype=float).flatten()
with open(path + '/704.0', 'w') as f:
ras_guess_file.tofile(f, sep='')
# Access to properties (only a reduced set should be accessible/editable)
@property
def molecule(self):
return self._molecule
@property
def mo_coefficients(self):
return self._mo_coefficients
@property
def mo_energies(self):
return self._scf_energies
@property
def scf_density(self):
return self._scf_density
@property
def hessian(self):
return self._hessian
@property
def ras_guess(self):
return self._ras_guess
@property
def gui(self):
return self._gui
@gui.setter
def gui(self, value):
value = int(value)
if value < 0 or value > 10:
raise ValueError('GUI value error')
self._gui = value
[docs] def update_input(self, dictionary):
"""
Update the input from data in a dictionary
Note: already existing parameters will be overwritten
:param dictionary: parameters to add
"""
# put to arguments self._* (will be written explicitly)
for name, value in dictionary.items():
setattr(self, '_' + name, value)
class CustomSection:
def __init__(self, title, keywords):
self._tile = title
self._data = keywords
def __hash__(self):
keywords = dict(self.__dict__)
digest = hashlib.md5(json.dumps(keywords, sort_keys=True).encode()).hexdigest()
return int(digest, 16)
def get_txt(self):
"""
create string with appropiate Q-Chem input section format
:return: string in Q-Chem input section format
"""
txt_input = '${}\n'.format(self._tile)
for prop, value in self._data.items():
txt_input += ' {} {}\n'.format(prop, value)
txt_input += '$end\n'
return txt_input